1
0
mirror of https://github.com/PDP-10/its.git synced 2026-01-26 20:22:22 +00:00
Files
PDP-10.its/src/hibou/itster.187
Adam Sampson 9d89a1d262 Use decimal I/O rather than octal.
From SV: HIBOU; HIBOU LISP. The high score table code assumes ~a formats
in decimal.
2018-07-29 00:09:25 +01:00

343 lines
11 KiB
Common Lisp
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;;; -*-LISP-*-
;;;
;;; Copyright (C) 2002 Donald Fisk
;;;
;;; This program is free software; you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 2 of the License, or
;;; (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program; if not, write to the Free Software
;;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
;;; USA
;;;; The original game was written Alexey Pajitnov, Dmitry Pavlovsky
;;;; and Vadim Gerasimov. This implementation is independent of it,
;;;; based on a specification I arrived at through playing the game
;;;; often. It is written in Maclisp for ITS. To run it, start
;;;; Maclisp `:LISP' and type `(load 'itster)' and then `(itster)' at
;;;; the prompt.
;;;; (rotation x y) contains the coordinates that the tile at (x . y) is
;;;; rotated to, relative to the top left corner of the 4x4 grid
;;;; containing the tile.
(array rotation t 4 4)
(defun init-rotation ()
(do ((x 0 (1+ x)))
((= x 4))
(do ((y 0 (1+ y)))
((= y 4))
(store (rotation x y) (cons (- 3 y) x)))))
(array shapes t 7)
(defun init-shapes ()
(store (shapes 0) '((0 . 1) (1 . 1) (2 . 1) (3 . 1)))
(store (shapes 1) '((0 . 1) (1 . 1) (2 . 1) (1 . 2)))
(store (shapes 2) '((0 . 1) (1 . 1) (2 . 1) (2 . 2)))
(store (shapes 3) '((0 . 1) (1 . 1) (1 . 2) (2 . 2)))
(store (shapes 4) '((1 . 1) (2 . 1) (1 . 2) (2 . 2)))
(store (shapes 5) '((2 . 1) (3 . 1) (1 . 2) (2 . 2)))
(store (shapes 6) '((2 . 1) (0 . 2) (1 . 2) (2 . 2))))
;;;; *current-shape* (i.e. the one currently falling) is a list of the
;;;; coordinates of its tiles. *x* and *y* are the coordinates of the top
;;;; left corner of the 4x4 grid the shape is in. The grid is useful when
;;;; calculating the initial position of the shape, and when rotating it.
(defvar +grid-x+ 10.)
(defvar +grid-y+ 5)
(defvar +num-columns+ 10.)
(defvar +num-rows+ 22.)
(defvar *current-shape*)
(defvar *next-shape*)
(defvar *x*)
(defvar *y*)
(defvar *score*)
;;;; *heap* initially stores the positions of imaginary tiles at the
;;;; perimeter of the rectangle the shapes fall in. During the game,
;;;; new tiles are added to heap when shapes land, and tiles are deleted
;;;; from heap when rows are completely filled.
(defvar *heap*)
(defun init-heap ()
(setf *heap* '())
;; Push on tiles to mark the perimeter of the grid. The y coord of
;; the last tile pushed = +num-rows+. We can use this to tell which tiles
;; fell and which tiles are perimeter markers.
(do ((y 0 (1+ y)))
((= y +num-rows+))
(push (cons -1 y) *heap*)
(push (cons +num-columns+ y) *heap*))
(do ((x 0 (1+ x)))
((= x +num-columns+))
(push (cons x +num-rows+) *heap*)))
(defun make-random-shape ()
(mapcar #'(lambda (tile)
(cons (+ *x* (car tile))
(+ *y* (cdr tile))))
(shapes (random 7))))
(defun print-next-shape ()
(cursorpos 0 (* 2 +grid-x+))
(princ "Next:")
(draw-shape (mapcar #'(lambda (tile)
(cons (car tile) (- (cdr tile) 5)))
*next-shape*)))
(defun get-new-current-shape ()
(setf *x* 3 *y* 0)
(if (boundp '*next-shape*)
(clear-shape (mapcar #'(lambda (tile)
(cons (car tile) (- (cdr tile) 5)))
*next-shape*))
(setf *next-shape* (make-random-shape)))
(setf *current-shape* *next-shape* *next-shape* (make-random-shape))
(print-next-shape)
*current-shape*)
;;; Tries to move shape one column left (x-move = -1), one column right
;;; (x-move = 1) or one row down (y-move = 1).
;;; Returns NIL on failure.
(defun move-shape (x-move y-move)
;; First, compute the new positions of the tiles.
(let ((new-shape (mapcar #'(lambda (tile)
(cons (+ x-move (car tile))
(+ y-move (cdr tile))))
*current-shape*)))
;; See if any new positions are on the heap.
(do ((tiles new-shape (cdr tiles)))
((or (null tiles)
;; New tile position already on heap?
(member (car tiles) *heap*))
(if (null tiles)
;; No new tile positions on heap -- move it (and return
;; new value of *y*).
(setf *current-shape* new-shape
*x* (+ x-move *x*) *y* (+ y-move *y*))
;; Fail -- do nothing and return NIL.
NIL)))))
;;; Tries to rotate shape. Returns NIL on failure.
(defun rotate-shape ()
(let ((new-shape (mapcar #'(lambda (tile)
;; Nasty, but might as well reuse lambda var.
(setf tile
(rotation (- (car tile) *x*)
(- (cdr tile) *y*)))
(cons (+ (car tile) *x*)
(+ (cdr tile) *y*)))
*current-shape*)))
;; See if any new positions are on the heap.
(do ((tiles new-shape (cdr tiles)))
((or (null tiles)
;; New tile position already on heap?
(member (car tiles) *heap*))
(if (null tiles)
;; No tile positions on heap -- return new value of
;; *current-shape*.
(setf *current-shape* new-shape)
;; Fail -- do nothing and return NIL.
NIL)))))
(defun draw-tile (tile)
;; Draw new position.
(cursorpos (+ +grid-y+ (cdr tile))
(* 2 (+ +grid-x+ (car tile))))
(princ '[]))
(defun draw-shape (shape) (mapc #'draw-tile shape))
(defun clear-shape (shape)
;; Clear previous positions if it can drop.
(mapc #'(lambda (tile)
(cursorpos (+ +grid-y+ (cdr tile))
(* 2 (+ +grid-x+ (car tile))))
(princ '| |))
shape))
(defun remove-duplicates (x)
(cond ((null x) x)
((member (car x) (cdr x)) (remove-duplicates (cdr x)))
(T (cons (car x) (remove-duplicates (cdr x))))))
(defun remove-whole-rows ()
;; Get the rows *current-shape* helped to fill.
(do ((rows (remove-duplicates (mapcar #'cdr *current-shape*))
(cdr rows))
(max-row 0))
((null rows)
;; Redraw heap. First clear down to max-row.
(do ((row 0 (1+ row)))
((> row max-row))
(cursorpos (+ +grid-y+ row) (* 2 +grid-x+))
(princ " "))
;; Now redraw the tiles down to max-row.
(do ((heap *heap* (cdr heap)))
((= (cdar heap) +num-rows+))
(if (<= (cdar heap) max-row)
(draw-tile (car heap)))))
(do ((heap *heap* (cdr heap))
(count 0))
((= (cdar heap) +num-rows+) ;From here on, it's
(if (= count +num-columns+) ; perimeter.
;; Row full. Update score.
(progn (setf *score* (1+ *score*))
;; Output new score at top of screen.
(cursorpos 0 0)
(format t "Score: ~a" *score*)
;; Update max row.
(setf max-row (max max-row (car rows)))
(do ((heap *heap* (cdr heap)))
((= (cdar heap) +num-rows+))
(cond ((< (cdar heap) (car rows))
;; Shift tile down a row.
(setf (cdar heap) (1+ (cdar heap))))
((= (cdar heap) (car rows))
;; Delete tile.
(setf *heap* (delq (car heap) *heap*)))))
;; Go through (cdr rows), shifting down rows above
;; (car rows).
(do ((remaining-rows (cdr rows) (cdr remaining-rows)))
((null remaining-rows))
(if (< (car remaining-rows) (car rows))
(setf (car remaining-rows)
(1+ (car remaining-rows))))))))
(if (= (cdar heap) (car rows))
;; Heap tile was in row.
(setf count (1+ count))))))
;;; Lowers shape and then responds to luser specified actions.
;;; Returns NIL if the game is over (*y* = 0).
(defun redraw-shape ()
(clear-shape *current-shape*)
;; Always try to lower shape one line.
(if (or (move-shape 0 1)
;; Add shape to heap if it can't drop any further.
(progn (mapc #'(lambda (tile) (push tile *heap*))
*current-shape*)
;; Redraw the old shape.
(draw-shape *current-shape*)
(remove-whole-rows)
(if (zerop *y*)
NIL ;Fail => game over.
;; Create a new shape.
(get-new-current-shape))))
;; Still in the game. Listen for luser specified actions.
(do ((char-to-read-p (listen) (listen))
(key))
((zerop char-to-read-p)
(draw-shape *current-shape*))
(setf key (readch))
;; These are not easy to locate and delete from screen, so use
;; unobtrusive ones.
(cond ((eq key '/,) (move-shape -1 0)) ;Left 1.
((eq key '/.) (move-shape 1 0)) ;Right 1.
((eq key '/`)
;; Lower shape.
(do ()
((null (move-shape 0 1)))))
((eq key '/') (rotate-shape))
((eq key '/ )
;; Useful if something overwrites screen.
(refresh))))))
(defun refresh ()
(cursorpos 'c) ;Clear screen.
(draw-perimeter)
(do ((heap *heap* (cdr heap)))
((= (cdar heap) +num-rows+))
(draw-tile (car heap)))
(cursorpos 0 0)
(format t "Score: ~a" *score*)
(print-next-shape))
(defun draw-perimeter ()
(cursorpos (1- +grid-y+) (1- (* 2 +grid-x+)))
(princ "+--------------------+")
(do ((line +grid-y+ (1+ line)))
((= line (+ +grid-y+ +num-rows+))
(cursorpos line (1- (* 2 +grid-x+)))
(princ "+--------------------+"))
(cursorpos line (1- (* 2 +grid-x+)))
(princ "| |")))
(defun itster ()
(setq base 10. ibase 10. *nopoint t)
(cursorpos 'c) ;Clear screen.
(format t "~%To move left, press ,~%~%")
(format t "To move right, press .~%~%")
(format t "To rotate, press '~%~%")
(format t "To drop onto the heap, press `~%~%")
(format t "Enter speed (if unsure, make it 5 or 10): ")
(let ((tick-length (quotient 1.0 (read))))
(cursorpos 'c) ;Clear screen.
(draw-perimeter)
(init-heap)
(init-rotation)
(init-shapes)
(setf *score* 0)
;; Output new score at top of screen.
(cursorpos 0 0)
(format t "Score: ~a" *score*)
(get-new-current-shape)
(do ()
((null (redraw-shape)) (update-scores *score*))
(sleep tick-length))))
(defun update-scores (score)
(let* ((f (open '((dsk games) itster scores) 'in))
;; Read old hall of fame.
(scores (read f)))
(close f)
(setf scores
(sort (cons (list score
(status uname)
(status dow)
(status date)
(status daytime))
scores)
#'(lambda (row1 row2) (> (car row1) (car row2)))))
(if (> (length scores) 10)
;; Remove lower score.
(rplacd (nthcdr 9 scores) '()))
(cursorpos 'c) ;Clear screen.
(format t "ITSter Hall of Fame~%===================~%")
(mapc #'(lambda (line)
(let* ((score (car line))
(uname (cadr line))
(dow (caddr line))
(date (cadddr line))
(day (caddr date))
(daytime (car (cddddr line))))
(format t
"~a ~a ~a ~a~a ~a, ~a ~a:~a:~a~%"
score uname dow
day
(if (member day '(11. 12. 13.))
"th"
(nth (remainder day 10.)
'("th" "st" "nd" "rd" "th"
"th" "th" "th" "th" "th")))
(nth (1- (cadr date))
'(Jan Feb Mar Apr May Jun
Jul Aug Sep Oct Nov Dec))
(+ 2000. (car date))
(car daytime) (cadr daytime) (caddr daytime))))
scores)
;; Save new hall of fame.
(setf f (open '((dsk games) itster scores) 'out))
(print scores f)
(close f)))