;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: DEMO-XASPERATE; Base: 10 -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;         The Garnet User Interface Development Environment.      ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This code was written as part of the Garnet project at          ;;;
;;; Carnegie Mellon University, and has been placed in the public   ;;;
;;; domain.  If you are using this code or any part of Garnet,      ;;;
;;; please contact garnet@cs.cmu.edu to be put on the mailing list. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  CHANGE LOG
;;;   2/17/93 - Andrew Mickish - Removed use of "OPAL" package
;;;  12/02/91 - Andrew Mickish - Added update of *score-win*

(in-package "DEMO-XASPERATE" :use '("LISP" "KR"))

(export '(do-go do-stop))

(defvar *margin* 15)
(defvar *space* 2)
(defvar *hole2* nil)
(defvar *hole1* nil)
(defvar *inter* nil)
(defvar *score* 0)

(defmacro half(x) `(round (* ,x 0.5)))

(defun do-go (&key dont-enter-main-event-loop double-buffered-p)
  (create-instance '*win* inter:interactor-window
        (:left 300)(:top 100)
	(:height 300)
	(:width  300)
        (:title "GARNET Xasperate")
        (:aggregate (create-instance '*agg* opal:aggregate)))
  (opal:add-component *agg*
	(create-instance '*big-text* opal:text
	   (:string "XASPERATE")
	   (:font (create-instance '*big-font* opal:font
			(:size :very-large)))
	   (:top (o-formula (half (- (gv *win* :height)
	                             (gvl :height)))))
	   (:left (o-formula (half (- (gv *win* :width)
	                              (gvl :width)))))))
  (opal:update *win*)
  (inter:beep)
;;(stall for some amount of time..)
  (s-value *big-text* :visible NIL)
  (create-instance '*score-win* opal::window
	(:top *margin*)
	(:left *margin*)
	(:width  (o-formula (max (- (gv *win* :width) 30) 1)))
	(:height 23)
	(:parent *win*)
	(:aggregate (create-instance '*score-agg* opal:aggregate)))
  (opal:update *score-win*)
  (opal:add-component *score-agg*
	(create-instance '*score-text* opal:text
	   (:string (o-formula (princ-to-string (gvl :score))))
	   (:score (setq *score* 0))
	   (:top 3)
	   (:left (o-formula (- (gv *score-win* :width) (gvl :width) 25)))))
  (create-instance '*play-win* inter:interactor-window
        (:double-buffered-p double-buffered-p)
	(:top (o-formula (+ (* 2 (gv *score-win* :top))
			    (- (gv *score-win* :height)
			       (gv *score-win* :top-border-width)
			       (gv *score-win* :bottom-border-width))
			    5)))
	(:left *margin*)
	(:width  (o-formula (- (gv *score-win* :width)
			       (gv *score-win* :left-border-width)
			       (gv *score-win* :right-border-width))))
	(:height (o-formula (max (- (gv *win* :height) (gvl :top) *margin*) 
				 1)))
	(:x-portion (o-formula (max (round (* (- (gvl :width) (* 3 *space*))
					      0.25))
				    2)))
	(:y-portion (o-formula (max (round (* (- (gvl :height) (* 4 *space*))
					      0.20))
				    2)))
	(:parent *win*)
	(:aggregate (create-instance '*play-agg* opal:aggregate)))

  (create-instance '*proto-object* opal:rectangle
	(:x-pos 0)   ;; from 0 to 3  LEFT
	(:y-pos 0)   ;; from 0 to 4  TOP
	(:x-size 1)  ;; from 1 to 2
	(:y-size 1)  ;; from 1 to 2
	(:filling-style opal:light-gray-fill)

	(:left (o-formula (* (gvl :x-pos) (+ (gv *play-win* :x-portion)
					     *space*))))
	(:top  (o-formula (* (gvl :y-pos) (+ (gv *play-win* :y-portion)
					     *space*))))
	(:width (o-formula
	  (let ((xsize (gvl :x-size)))
		(+ (* xsize (gv *play-win* :x-portion))
		   (* (1- xsize) *space*)))))
	(:height (o-formula
	  (let ((ysize (gvl :y-size)))
		(+ (* ysize (gv *play-win* :y-portion))
		   (* (1- ysize) *space*))))))

;; The 4 little guys
  (dolist (i '(1 2))
   (dolist (j '(3 4))
    (opal:add-component *play-agg*
	(create-instance nil *proto-object*
		(:x-pos i)
		(:y-pos j)))))

;; The 4 tall guys
  (dolist (i '(0 3))
   (dolist (j '(0 2))
    (opal:add-component *play-agg*
	(create-instance nil *proto-object*
		(:x-pos i)
		(:y-pos j)
		(:y-size 2)
		(:filling-style opal:gray-fill)))))

;; The 1 wide guy
  (opal:add-component *play-agg*
    (create-instance nil *proto-object*
	(:x-pos 1)
	(:y-pos 2)
	(:x-size 2)
	(:filling-style opal:gray-fill)))

;; The 1 big guy
  (opal:add-component *play-agg*
    (create-instance nil *proto-object*
	(:x-pos 1)
	(:y-pos 0)
	(:x-size 2)
	(:y-size 2)
	(:filling-style opal:dark-gray-fill)))

;; The 'blank' guys...
  (setq *hole1* (create-instance nil *proto-object*
			(:x-pos 0)
			(:y-pos 4)))
  (setq *hole2* (create-instance nil *proto-object*
			(:x-pos 3)
			(:y-pos 4)))
  (opal:update *win*)

  (create-instance '*inter* inter:two-point-interactor
    (:window *play-win*)
    (:start-where T)
    (:line-p T)
    (:abort-if-too-small nil)
    (:running-action nil)
    (:final-function
  #'(lambda (an-interactor final-point-list)
     (declare (ignore an-interactor))
     (let* ((first-x (first final-point-list))
	    (first-y (second final-point-list))
	    (last-x  (third final-point-list))
            (last-y  (fourth final-point-list))
	    (from-obj (opal:point-to-component *play-agg* first-x first-y)))
	(when from-obj
	  (cond
	   ((opal:point-in-gob *hole1* last-x last-y)
	    (do-move from-obj *hole1*))
	   ((opal:point-in-gob *hole2* last-x last-y)
	    (do-move from-obj *hole2*))
	   ((eq (opal:point-to-component *play-agg* last-x last-y) from-obj)
	     (if (adjacent? from-obj *hole1*)
		(do-move from-obj *hole1*)
		(do-move from-obj *hole2*)))))))))
  (format t "~%~%")
  (format t "Demo Xasperate!!!!!~%~%")
  (format t "Move objects with the left mouse button by either clicking on the object,~%")
  (format t "or -- if there is more than one legal move from an object -- dragging from~%")
  (format t "the object to its destination.~%~%")
  (format t "The object is to move the large dark rectangle so that it is resting on the~%")
  (format t "bottom of the grid.  This can be done in 86 moves.~%~%")
  (format t "This demo is a Garnet version of the game 'xasperate' found in X11tra.  There~%")
  (format t "are two differences, however:~%")
  (format t "   (1) Just clicking on an object moves it in this version; and~%")
  (format t "   (2) If you change the size of the window, the game pieces change size!~%~%")
  (format t "Good luck, and enjoy!!!!~%~%")

  (unless dont-enter-main-event-loop #-cmu (inter:main-event-loop))

)

(defun adjacent? (obj1 obj2)
  (let ((xpos1 (g-value obj1 :x-pos))  (xsize1 (g-value obj1 :x-size))
	(xpos2 (g-value obj2 :x-pos))  (xsize2 (g-value obj2 :x-size))
	(ypos1 (g-value obj1 :y-pos))  (ysize1 (g-value obj1 :y-size))
	(ypos2 (g-value obj2 :y-pos))  (ysize2 (g-value obj2 :y-size)))
    (or  (and
	   (= xpos1 xpos2)
	   (or
	     (= ypos2 (+ ypos1 ysize1))
	     (= ypos1 (+ ypos2 ysize2))))
	 (and
	   (= ypos1 ypos2)
	   (or
	     (= xpos2 (+ xpos1 xsize1))
	     (= xpos1 (+ xpos2 xsize2)))))))

(defun do-move (obj hole)
  (let* ((xpos1 (g-value obj  :x-pos))  (xsize1 (g-value obj  :x-size))
	 (xpos2 (g-value hole :x-pos))  (xsize2 (g-value hole :x-size))
	 (ypos1 (g-value obj  :y-pos))  (ysize1 (g-value obj  :y-size))
	 (ypos2 (g-value hole :y-pos))  (ysize2 (g-value hole :y-size))
	 (ohole (if (eq hole *hole1*) *hole2* *hole1*))
	 (ohole-xpos (g-value ohole :x-pos))
	 (ohole-ypos (g-value ohole :y-pos))
	 (beep-needed? T)
	 (fix-other-hole? NIL)
	 temp)
    (if (= xpos1 xpos2)
     (if (= ypos2 (+ ypos1 ysize1))
	; code for block above hole, first check for ohole above, to right...
	(when (or (= xsize1 1)
		  (and (= ohole-xpos (1+ xpos1))
		       (= ohole-ypos ypos2)
		       (setq fix-other-hole? T)))
		(setq beep-needed? nil)
	        (s-value hole :y-pos ypos1)
		(if fix-other-hole? (s-value ohole :y-pos ypos1))
		(s-value obj :y-pos (1+ ypos1))
		(s-value *score-text* :score (incf *score*)))
	(if (= ypos1 (+ ypos2 ysize2))
	  ; code for block below hole, first check for ohole below, to right..
	  (when (or (= xsize1 1)
		    (and (= ohole-xpos (1+ xpos1))
		         (= ohole-ypos ypos2)
			 (setq fix-other-hole? T)))
		  (setq beep-needed? nil)
	          (s-value hole :y-pos (setq temp (1- (+ ypos1 ysize1))))
		  (if fix-other-hole? (s-value ohole :y-pos temp))
		  (s-value obj :y-pos (1- ypos1))
		  (s-value *score-text* :score (incf *score*)))))
     ; so not in same column...
    (if (= ypos1 ypos2)
     (if (= xpos2 (+ xpos1 xsize1))
	; code for block left of hole
	(when (or (= ysize1 1)
		  (and (= ohole-ypos (1+ ypos1))
		       (= ohole-xpos xpos2)
		       (setq fix-other-hole? T)))
		(setq beep-needed? nil)
	        (s-value hole :x-pos xpos1)
		(if fix-other-hole? (s-value ohole :x-pos xpos1))
		(s-value obj :x-pos (1+ xpos1))
		(s-value *score-text* :score (incf *score*)))
	(if (= xpos1 (+ xpos2 xsize2))
	  ; code for block right of hole
	  (when (or (= ysize1 1)
		    (and (= ohole-ypos (1+ ypos1))
		         (= ohole-xpos xpos2)
		         (setq fix-other-hole? T)))
		  (setq beep-needed? nil)
	          (s-value hole :x-pos (setq temp (1- (+ xpos1 xsize1))))
		  (if fix-other-hole? (s-value ohole :x-pos temp))
		  (s-value obj :x-pos (1- xpos1))
		  (s-value *score-text* :score (incf *score*)))))))
    (if beep-needed?
     (inter:beep))))

(defun do-stop()
  (opal:destroy *win*))
