;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: Eyehand -*- ;;; Microworld implementation ;;; [This file was inspired by Ramstad's implementation of Drescher's ;;; system, but Ramstad's version has by now been 80% or more rewritten.] ;;; This file implements a simple software simulation of a world ;;; it contains variables, definitions and functions for: ;;; Objects in the world and their characteristics. ;;; 140+ functions which can be called to get the status of each ;;; primitive item. ;;; 17 functions which correspond to the primitive actions. ;;; CLOCK-TICK (a function which randomizes object position, ;;; opens the hand after 3 time units of being closed, ;;; and increments the clock). ;;; Keeping the state of the world, initializing it, and loading/saving ;;; the random number generator seed. ;;; [Actually, many of these have been eliminated or moved out of this file. -- Foner] ;;; Note: the state of the microworld is kept internally. Nothing ;;; other than t/nil/status messages are returned by any function. (in-package :eyehand) (proclaim '(fixnum *hp-x* *hp-y* *vp-x* *vp-y* *clock* *grip-expiration*)) (proclaim '(atom *hcl* *hgr*)) ;;; Build the macros we'll need. (def-microworld EYEHAND) ;;; Note on coordinate systems: ;;; There are three coordinate systems which are used within this ;;; file: microspace, body and glance relative coordinates. ;;; In each case, the X axis (first position in the coordinate pair) ;;; runs left to right while the Y axis (second position) runs bottom ;;; to top, i.e. traditional mathematical notation. ;;; Microspace relative coordinates reference the 7x7 world array ;;; directly where the lower left hand corner is position (0,0) and ;;; the lower right hand corner is position (6,0). ;;; Body relative coordinates are often used when referring to the ;;; center 3x3 area in the microworld. ;;; The lower left corner of this area is microspace coordinate (2,2) ;;; which is defined as body relative coordinate (0,0). ;;; Glance relative coordinates are used when referring to the 5x5 ;;; area centered around the current glance orientation. ;;; The current glance orientation is stored as microspace relative ;;; coordinates in *VP-X* and *VP-Y*. ;;; The center of the 5x5 glance relative area is defined as glance ;;; relative coordinate (2,2) with the lower left corner of this area ;;; defined as glance relative coordinate (0,0). ;;; The world is modeled as a 7x7 "microspace" which is ;;; represented as a 2D array, with indices 0 through 6. ;;; NOTE! If you change this value, you MUST also change the PROCLAIM ;;; below. Since *WORLD-DIAMETER* isn't defined until load-time, but ;;; the optimization must be known at compile-time, this is somewhat ;;; problematic unless I arranged to have *WORLD-DIAMETER* in some ;;; previous file just for the PROCLAIM's benefit. (deflimit *WORLD-DIAMETER* 7 7) (defvar *WORLD* (make-array (list *world-diameter* *world-diameter*) :initial-element nil)) ;;; &&& VARIABLE-PROCLAIMATION PROBLEM. Temporarily commented out. --- Well, not really, but must fix this case! ;;; The values here must stay in sync with the DEFLIMIT *WORLD-DIAMETER* above! (proclaim '(type (simple-array t (7 7)) *world*)) ;;; Brief accessor for readability. (defmacro GET-OBJECT (x y) `(aref *world* ,x ,y)) ;;;; World-object datatype. ;;; World objects have three sets of characteristics: ;;; visual, tactile and taste, each of which is represented as ;;; an array of t or nil elements. ;;; In addition, objects can be marked either movable or not. ;;; Currently, only the body and hand are considered to be immovable ;;; objects. ;;; Movable objects can be moved by the hand, and if not currently ;;; grasped will be randomly moved occasionally by the CLOCK-TICK ;;; function in a direction indicated by the current cycle value. ;;; 0 indicates the next move is to the right, 1 down, 2 left, 3 up. ;;; i.e. the objects move in a clockwise direction over time. (defstruct WORLD-OBJECT (visual (make-array 16 :initial-element nil) :type (simple-array atom (16))) (tactile (make-array 4 :initial-element nil) :type (simple-array atom (4))) (taste (make-array 4 :initial-element nil) :type (simple-array atom (4))) (movable t :type atom) (cycle 0 :type fixnum)) ;;; Brief accessors for readability. (defmacro GET-VISUAL (object index) (declare (fixnum index)) `(the atom (svref (world-object-visual ,object) ,index))) (defmacro GET-TACTILE (object index) (declare (fixnum index)) `(the atom (svref (world-object-tactile ,object) ,index))) (defmacro GET-TASTE (object index) (declare (fixnum index)) `(the atom (svref (world-object-taste ,object) ,index))) (defmacro GET-MOVABLE (object) `(the atom (world-object-movable ,object))) ;;; This definition seems to give SETF the willies, at least in HCL. ; (defmacro GET-CYCLE (object) ; `(the fixnum (world-object-cycle ,object))) (defmacro GET-CYCLE (object) `(world-object-cycle ,object)) ;;;; Defining the body and hand objects. (defvar *BODY* (make-world-object :visual (make-array 16 :initial-contents '(t t t t t nil nil t t nil nil t t t t t)) :tactile (make-array 4 :initial-contents '(t t nil nil)) :taste (make-array 4 :initial-contents '(t nil nil nil)) :movable nil)) (defvar *HAND* (make-world-object :visual (make-array 16 :initial-contents '(t t t t t t t t nil nil t t t t t t)) :tactile (make-array 4 :initial-contents '(t nil t nil)) :taste (make-array 4 :initial-contents '(t t t nil)) :movable nil)) ;;;; Defining the left and right hand objects. (defvar *OBJECT-1* (make-world-object :visual (make-array 16 :initial-contents '(nil t t nil t nil nil t t nil nil t nil t t nil)) :tactile (make-array 4 :initial-contents '(nil nil t t)) :taste (make-array 4 :initial-contents '(nil t t nil)) :movable t :cycle 0)) (defvar *OBJECT-2* (make-world-object :visual (make-array 16 :initial-contents '(t t nil nil nil t t nil nil nil t t nil nil nil t)) :tactile (make-array 4 :initial-contents '(nil t nil nil)) :taste (make-array 4 :initial-contents '(nil t nil t )) :movable t :cycle 1)) (defun COMPARE-DETAILS (&key (objects (list *body* *hand* *object-1* *object-2*)) (accessor #'world-object-visual)) (mapcar #'(lambda (object) (listarray (funcall accessor object))) objects)) (defun SHOW-COMPARE-DETAILS (&key (objects (list *body* *hand* *object-1* *object-2*)) (accessor #'world-object-visual)) (let ((details (compare-details :objects objects :accessor accessor))) (loop for detail in details do (format t "~&~S~&" (mapcar #'(lambda (value) (if value 1 0)) detail)))) (values)) ;;; Note on the body relative coordinate system: ;;; body relative coordinates are often used when referring to the ;;; center 3x3 area in the microworld. ;;; The lower left corner of this area is defined as body relative ;;; coordinate (0,0) which corresponds to microspace coordinate (2,2). ;;; The position of the body in body relative coordinates is (1,-1) ;;; which corresponds to microspace coordinate (3,1). (defconstant *BODY-X* 1) (defconstant *BODY-Y* -1) ;;; *CLOCK* is incremented with each call to the function CLOCK-TICK. ;;; *GRIP-EXPIRATION* is used by the CLOCK-TICK function to ensure ;;; that the hand is not closed for more than 3 consecutive time units. (defvar *CLOCK* 0) (defvar *GRIP-EXPIRATION* -1) ;;;; *** start of primitive item definitions *** ;;; The primitive items are implemented as functions which return ;;; values which indicate the current state of a particular item. ;;; The value T indicates the item is currently on. ;;; The value NIL indicates the item is currently off. ;;; Collectively, they indicate those states of the microworld ;;; which the simulated robot is currently able to sense. ;;; I'd like to make the four macros below usable across packages without just ;;; copying their code, a la what I did with DEF-DEFITEM, but, unlike the four ;;; macros defined by DEF-DEFITEM, each macro requires the one before it ;;; to expand correctly, and this means I can't just make a form that expands into a ;;; bunch of DEFMACRO forms, since those would be expanded in parallel. I could ;;; probably find a way around this, but it seems like too much work right now. ;;; Note that the ERROR-CHECKING-FORM used by DEF-ITEMS-INTERNAL via ;;; DEF-ITEM-GENERATOR (and hence available in generators defined by DEF-ITEMS-1D ;;; and DEF-ITEMS-2D) is presumably some ASSERT form which will fail if, for ;;; example, a limit is set wrong. We evaluate it _after_ doing the internal DEFLIMIT ;;; (since otherwise we really don't have an appropriate scope for the form to use ;;; in checking anything---the limit variable isn't set yet, and CURRENT-LIMIT isn't ;;; bound here), which means that, if the assertion blows out, the limit has ;;; _already_ been changed (possibly propagating changes to other limits, too). This ;;; shouldn't be a problem if limit initialization forms don't have side-effects (which ;;; it is documented that they shouldn't), and if you eventually fix whatever problem ;;; caused the assertion to blow out and reevaluate the DEF-ITEMS-2D (or whatever) ;;; form (which you presumably will, since it blew out in the middle of compiling it). ;;; ;;; Note that we _also_ pass the ERROR-CHECKING-FORM to DEFLIMIT itself to check. ;;; So if that inner check blows out, we won't wind up making any other ;;; side-effects. (Although it's certainly possible to write some assertion that ;;; doesn't check the inner use of DEFLIMIT at all, but still blows out in the outer ;;; check, after other things have been done---though I expect this to be rather ;;; unlikely. But just in case, we also check in DEF-ITEMS-INTERNAL as well as giving ;;; the form to DEFLIMIT.) ;;; Used by DEF-ITEM-GENERATOR below. (defmacro-definer DEF-ITEMS-INTERNAL (limit-name current-limit historical-limit base-name error-checking-form &body builder-body) `(progn (deflimit ,limit-name ,current-limit ,historical-limit ,error-checking-form) (def-simple-item-naming-function ,base-name) ,(when error-checking-form error-checking-form) ,@builder-body)) ;;; Builds a macro named GENERATOR-NAME whose arglist is as shown by the line with ;;; the "***" comment below. The body therefore gets those particularly-named ;;; args to work with. (defmacro-definer DEF-ITEM-GENERATOR (generator-name &body generator-body) `(defmacro ,generator-name (limit-name current-limit historical-limit base-name item-fn &optional error-checking-form); *** Generator's arglist. (let* ((namer (intern-format "simple-item-naming-function-~A" base-name)) ; Hmm. I don't like have to mention it explicitly like this... (forms ,@generator-body)) `(progn (def-items-internal ,limit-name ,current-limit ,historical-limit ,base-name ,error-checking-form ,@forms))))) ;;; Note that it's actually possible to recompile a DEF-ITEMS-1D or DEF-ITEMS-2D ;;; form without destroying the state of the world, subject to a few conditions: ;;; The new number of items generated by the form is not less than the old number. ;;; (This is because nothing will remove the no-longer-generated items from the list ;;; in *PRIMITIVE-ITEMS*, so they'll continue to exist in *ITEM-ARRAY* and get called ;;; by the schema mechanism.) ;;; You make sure that the next run you do starts a new run (rather than continuing ;;; an old one), so MAKE-ALL-ITEMS runs, which will properly update various datastructures, ;;; such as *NUMBER-OF-PRIMITIVE-ITEMS*. ;;; To solve the problem in the first limitation above, I suppose that I could make a ;;; form that zaps all the items with names "similar to" those generated by the ;;; simple-item-naming-function, to be used before recompiling one of these forms ;;; in a way that would try to decrease the number of items generated, for use in ;;; patch files. I don't feel comfortable having _every_ use of one of these ;;; generators calling it, though (hence I shouldn't make it part of the ;;; macroexpansion of the generator). ;;; ;;; To solve the problem in the second limitation above, I suppose I could make any ;;; use of these forms set a global variable that warns and/or forces a new run ;;; (unless I happen to be clever & careful enough to know that what I've done is no ;;; cause for a new run, and reset it). This might be justified. (def-item-generator DEF-ITEMS-1D (loop for index from 0 below current-limit collect `(defitem-built ,item-fn ,namer ,index))) (def-item-generator DEF-ITEMS-2D (loop for x from 0 below current-limit append (loop for y from 0 below current-limit collect `(defitem-built ,item-fn ,namer ,x ,y)))) ;;;; Haptic-proprioceptive items hp00-hp22 ;;; The variables *HP-X* and *HP-Y* store the current body relative ;;; hand position - each ranges from 0-2. (defvar *HP-X* 1) (defvar *HP-Y* 0) ;;; HP-CHECK returns T if X and Y give the current hand position. (defmacro HP-CHECK (x y) (declare (fixnum x y)) `(and (fix= *hp-x* ,x) (fix= *hp-y* ,y))) ;;; Definition of the HP item functions via the HP-CHECK macro. (def-items-2d *HP-DIAMETER* 3 3 hp hp-check) ;;;; Visual-proprioceptive items vp00-vp22. ;;; The variables *VP-X* and *VP-Y* store the current glance ;;; orientation - each ranges from 0-2. ;;; They are microspace relative coordinates which designate the ;;; center of the 5x5 visual field. (defvar *VP-X* 1) (defvar *VP-Y* 1) ;;; VP-CHECK returns T if X and Y give the current glance orientation. (defmacro VP-CHECK (x y) (declare (fixnum x y)) `(and (fix= *vp-x* ,x) (fix= *vp-y* ,y))) ;;; Definition of the VP item functions via the VP-CHECK macro. (def-items-2d *VP-DIAMETER* 3 3 vp vp-check) ;;; Note on the glance relative coordinate system: ;;; glance relative coordinates are used when referring to the 5x5 ;;; area centered around the current glance orientation. ;;; The center of this area is defined as glance relative coordinate ;;; (2,2) with the lower left corner of this area defined as glance ;;; relative coordinate (0,0). ;;; The current glance orientation is stored as microspace relative ;;; coordinates in *VP-X* and *VP-Y*. ;;;; Coarse visual-field items vf00-vf44. ;;; VF-CHECK returns T if an object is at the given ;;; glance relative position - if empty, returns NIL. (defmacro VF-CHECK (x y) (declare (fixnum x y)) `(if (get-object (fix+ *vp-x* ,x) (fix+ *vp-y* ,y)) t nil)) ;;; Definition of the vf item functions via the VF-CHECK macro. (def-items-2d *VF-DIAMETER* 5 5 vf vf-check (assert (oddp *vf-diameter*))) ; Otherwise, we don't know where to center the fovea! ;;;; Visual detail items: ;;;; fovf00-33, fovb00-33, fovl00-33, fovr00-33, fovx00-33. ;;; Each FOV-CHECK returns one of the visual details ;;; associated with the object found at the given foveal ;;; area - each detail is either T or NIL. ;;; Note that the foveal areas are determined by the current ;;; microspace relative glance orientation. ;;; Note that the 2D items are kept internally in a 1D array so it is ;;; necessary to translate between the two representations when ;;; referencing the get-visual macro. ;;; Further note that this translation happens at compile time, not ;;; run time, so it doesn't slow down the execution of the code. (defmacro FOVF-CHECK (x y) (declare (fixnum x y)) `(let ((foo (get-object (fix+ 2 *vp-x*) (fix+ 3 *vp-y*)))) (and foo (get-visual foo ,(+ (* x 4) y))))) (defmacro FOVB-CHECK (x y) (declare (fixnum x y)) `(let ((foo (get-object (fix+ 2 *vp-x*) (fix1+ *vp-y*)))) (and foo (get-visual foo ,(+ (* x 4) y))))) (defmacro FOVL-CHECK (x y) (declare (fixnum x y)) `(let ((foo (get-object (fix1+ *vp-x*) (fix+ 2 *vp-y*)))) (and foo (get-visual foo ,(+ (* x 4) y))))) (defmacro FOVR-CHECK (x y) (declare (fixnum x y)) `(let ((foo (get-object (fix+ 3 *vp-x*) (fix+ 2 *vp-y*)))) (and foo (get-visual foo ,(+ (* x 4) y))))) (defmacro FOVX-CHECK (x y) (declare (fixnum x y)) `(let ((foo (get-object (fix+ 2 *vp-x*) (fix+ 2 *vp-y*)))) (and foo (get-visual foo ,(+ (* x 4) y))))) (def-items-2d *FOVF-DIAMETER* 4 4 fovf fovf-check) (def-items-2d *FOVB-DIAMETER* 4 4 fovb fovb-check) (def-items-2d *FOVL-DIAMETER* 4 4 fovl fovl-check) (def-items-2d *FOVR-DIAMETER* 4 4 fovr fovr-check) (def-items-2d *FOVX-DIAMETER* 4 4 fovx fovx-check) ;;;; Coarse tactile items for each side of the hand: ;;;; tactf, tactb, tactr, tactl. ;;; Return T if an object is present, NIL if not. ;;; T or NIL is returned explicitly so the user cannot 'accidentally' ;;; get an object structure. ;;; Note that the object positions checked are the microspace ;;; coordinates indicated by the current body relative position of the ;;; hand. (defitem TACTF (if (get-object (fix+ 2 *hp-x*) (fix+ 3 *hp-y*)) t nil)) (defitem TACTB (if (get-object (fix+ 2 *hp-x*) (fix1+ *hp-y*)) t nil)) (defitem TACTR (if (get-object (fix+ 3 *hp-x*) (fix+ 2 *hp-y*)) t nil)) (defitem TACTL (if (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) t nil)) ;;;; Coarse tactile items for each side of the body; ;;;; bodyf, bodyb, bodyr, bodyl. ;;; Again, return T if an object is present, NIL if not. ;;; T or NIL is returned explicitly so the user cannot 'accidentally' ;;; get an object structure. ;;; Note that the object positions checked are the microspace ;;; coordinates indicated by the current body relative position of the ;;; body. (defitem BODYF (if (get-object (fix+ 2 *body-x*) (fix+ 3 *body-y*)) t nil)) (defitem BODYB (if (get-object (fix+ 2 *body-x*) (fix1+ *body-y*)) t nil)) (defitem BODYR (if (get-object (fix+ 3 *body-x*) (fix+ 2 *body-y*)) t nil)) (defitem BODYL (if (get-object (fix1+ *body-x*) (fix+ 2 *body-y*)) t nil)) ;;;; Detailed tactile items for objects touching the left edge (i.e. ;;;; "fingers") of the hand: text0-3. ;;; Each detail is T or NIL. ;;; Note that the position checked is the microspace coordinate ;;; indicated as the position to the left of the current body relative ;;; position of the hand. (defmacro TEXT-CHECK (x) (declare (fixnum x)) `(let ((foo (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)))) (when foo (get-tactile foo ,x)))) (def-items-1d *TEXT-DIAMETER* 4 4 text text-check) ;;;; Detailed taste items for objects touching the front (i.e. "mouth") ;;;; of the body: taste0-3. ;;; Each detail is T or NIL. ;;; Note that the position checked is the microspace coordinate ;;; indicated as the position to the front of the current body relative ;;; position of the body. (defmacro TASTE-CHECK (x) (declare (fixnum x)) `(let ((foo (get-object (fix+ 2 *body-x*) (fix+ 3 *body-y*)))) (when foo (get-taste foo ,x)))) (def-items-1d *TASTE-DIAMETER* 4 4 taste taste-check) ;;;; Hand closed item: hcl. (defvar *HCL* nil) (defitem HCL *hcl*) ;;;; Hand closed and grasping something item: hgr. (defvar *HGR* nil) (defitem HGR *hgr*) ;;;; *** start of primitive action definitions *** ;;; The primitive actions are implemented as functions which may or ;;; may not change the microworld state. ;;; These functions do not return meaningful values. ;;;; Hand motion functions: handf, handb, handr, handl. ;;; Each function first checks the new position for conflicts ;;; (out of the 3x3 range, object already occupying new position) ;;; and if none, moves the hand by first moving any grasped object, ;;; and then moving the hand. ;;; Note that the destination square for both the hand and any grasped ;;; object must be empty for the hand movement to take place ;;; (however, when the hand is closed and grasping an object and moves ;;; left, the hand will occupy the former position of the grasped ;;; object so only the position left of the object needs to be empty, ;;; and similarly for moving right with a grasped object, the object ;;; will occupy the former position of the hand so only the position ;;; to the right of the hand needs to be empty). ;;; Stolen from SCHEMA::FAKE-NEW-LOCATION. I probably shouldn't have two copies ;;; of the code, but the SCHEMA version is a hack anyway. ;;; ;;; Computes where the new location will be. (defun DIRECTION->NEW-LOCATION (direction x y) #+Genera (declare (values new-x new-y)) (ecase direction (:up (values x (1+ y))) (:down (values x (1- y))) (:left (values (1- x) y)) (:right (values (1+ x) y)))) ;;; %%% Where to go from here: (This comment written 6 Nov, but it's about ;;; code I wrote in early Oct...) use DIRECTION->NEW-LOCATION to compute where ;;; things are for HANDx stuff. Test using the world-state-shower I wrote, whatever ;;; its name is. Make sure objects move correctly as well as the hand. ;;; ;;; Then, emit opcodes for hand & object motion. Do same for eye. Do same for ;;; grasp/ungrasp. Make a meter to hold all this stuff. Probably should put all ;;; events that happen in one action in same cluster (e.g., hand motion and object ;;; motion), since I can always flatten it out later, and this way the meter's entries ;;; map one-to-one with clock ticks. ;;; ;;; Note also that we define a meter below (*INANIMATE-OBJECT-MOTIONS*) and ;;; use it in CLOCK-TICK-MAYBE-MOVE-OBJECTS for the circular object motions. ;;; Should flush that meter and just add such motions to the meter we define ;;; for dealing with this stuff here. ;;; Need to compute new coords and store them in the meter! (defaction HANDF (unless (or (fix> *hp-y* 1) (get-object (fix+ 2 *hp-x*) (fix+ 3 *hp-y*)) (and *hgr* (get-object (fix1+ *hp-x*) (fix+ 3 *hp-y*)))) (when *hgr* (setf (get-object (fix1+ *hp-x*) (fix+ 3 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) nil)) (setf (get-object (fix+ 2 *hp-x*) (fix+ 3 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) nil *hp-y* (fix1+ *hp-y*)))) (defaction HANDB (unless (or (fix< *hp-y* 1) (get-object (fix+ 2 *hp-x*) (fix1+ *hp-y*)) (and *hgr* (get-object (fix1+ *hp-x*) (fix1+ *hp-y*)))) (when *hgr* (setf (get-object (fix1+ *hp-x*) (fix1+ *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) nil)) (setf (get-object (fix+ 2 *hp-x*) (fix+ 1 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) nil *hp-y* (fix1- *hp-y*)))) (defaction HANDR (unless (or (fix> *hp-x* 1) (get-object (fix+ 3 *hp-x*) (fix+ 2 *hp-y*))) (setf (get-object (fix+ 3 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*))) (cond (*hgr* (setf (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)) nil)) (t (setf (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) nil))) (setf *hp-x* (fix1+ *hp-x*)))) (defaction HANDL (unless (or (fix< *hp-x* 1) (and *hgr* (get-object *hp-x* (fix+ 2 *hp-y*))) (and (not *hgr*) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)))) (when *hgr* (setf (get-object *hp-x* (fix+ 2 *hp-y*)) (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)))) (setf (get-object (fix+ 1 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) nil *hp-x* (fix1- *hp-x*)))) ;;;; Glance orientation functions: eyef, eyeb, eyer, eyel. ;;; Each function merely checks to make sure that the glance ;;; orientation variable will still be in the range 0-2 inclusive ;;; after modification, and then performs the modification. ;;; This is the "clearance" between the diameter of the course visual retina and the ;;; diameter of the microworld itself (the former is nested inside the latter like a ;;; planetary gear). Thus, we cannot move the retina more than this clearance from ;;; the 0,0 point without having it hang off an edge. (deflimit *WORLD-VF-CLEARANCE* (- *world-diameter* *vf-diameter*) 2 ;; VF must be no larger than the world. (If it's the same size, we can't ;; shift our gaze at all---'cause there's no where else to look---but ;; that's okay, if pathological. But having it larger means we're looking ;; past the edges of the universe!) (assert (not (minusp *world-vf-clearance*)))) (defaction EYEF (when (fix< *vp-y* *world-vf-clearance*) (setf *vp-y* (fix1+ *vp-y*)))) (defaction EYEB (when (fix> *vp-y* 0) (setf *vp-y* (fix1- *vp-y*)))) (defaction EYER (when (fix< *vp-x* *world-vf-clearance*) (setf *vp-x* (fix1+ *vp-x*)))) (defaction EYEL (when (fix> *vp-x* 0) (setf *vp-x* (fix1- *vp-x*)))) ;;;; Hand closing and opening functions: grasp, ungrasp. ;;; This function closes the hand, grasping any movable object ;;; touching the left edge of the hand (unless hand was already closed). ;;; The hand stays closed until 3 time units pass or until explicitly ;;; opened. (defaction GRASP (unless *hcl* (let ((object (get-object (fix1+ *hp-x*) (fix+ 2 *hp-y*)))) (when (and object (get-movable object)) (setf *hgr* t))) (setf *hcl* t *grip-expiration* (fix+ 3 *clock*)))) ;;; This function opens the hand. (defaction UNGRASP (setf *hcl* nil *hgr* nil *grip-expiration* -1)) ;;;; Time-keeping function CLOCK-TICK ;;; Each time unit should correspond to one primitive action taken. ;;; This function should be called after each primitive action and ;;; before the schema mechanism takes the statistics for this time ;;; step - it returns the new value of *CLOCK*. ;;; The grip expires and the hand automatically opens after 3 time ;;; units from when it was first closed ;;; (i.e. if grasp is the action selected at time x, the hand will ;;; remain closed for time x+1, time x+2 and time x+3, unless ;;; explicitly opened during one of these time steps. If it is not ;;; explicitly opened, after the action is executed for time x+3, the ;;; following call to clock-tick will open the hand and set the time ;;; equal to x+4). ;;; On average, every 200 calls to this function randomly moves all of ;;; the non-grasped movable objects in the world (unless moving a ;;; particular object would result in a collision with another object). ;;; (1:200 -> 1,000:200,000 for additional randomness.) ;;; The direction moved is indicated by the current cycle value for ;;; the object in question. ;;; 0 indicates the next move is to the right, 1 down, 2 left, 3 up. ;;; I.e., the objects move in a clockwise direction over time. ;;; The stuff on this page was totally rewritten on 28 Sep 93 to break out the logic ;;; for object motions and record it for later playback in an animation. (schema::def-vector-metering-counter *inanimate-object-motions*) ;;; Unbelievably enough, an individual object has no idea what it is! So we'll look it ;;; up, erring if it's nothing we know. *sigh* I could fix this, but then dumped ;;; worlds couldn't be reloaded, probably. We can't use EQ to check, either, because ;;; the act of restoring a snapshot creates a _copy_ of the object which is no longer ;;; EQ to the original. So taste them instead. (This depends on them tasting different, ;;; of course, but happens to be true here. A new cut at this, or any other microworld, ;;; would explicitly represent object identities, not for use by items [that'd be cheating], ;;; but for use by monitoring, metering, and debugging code, which is what we have here. (defun IDENTIFY-OBJECT (object) (let ((taste (world-object-taste object))) (loop for object in (list *object-1* *object-2* *hand* *body*) ; This conses four words per test. So sue me. for name in '(:object-1 :object-2 :hand :body) when (equalp taste (world-object-taste object)) do (return name)))) ;;; A paranoid version that'll blow out if not handed one of *OBJECT-1* or *OBJECT-2*. (defun IDENTIFY-INANIMATE-OBJECT-ONLY (object) (let ((id (identify-object object))) (unless (member id '(:object-1 :object-2)) (error "~S is identified as ~S, but it must be one of :OBJECT-1 or :OBJECT-2." object id)) id)) ;;; &&& Yuck! This apparently just scans the entire world looking for objects. ;;; &&& Surely, as I scale up the world, this should be replaced by something ;;; &&& that tracks the objects directly, since otherwise it gets O(n^2) slower. (defun CLOCK-TICK-MAYBE-MOVE-OBJECTS () (flet ((do-move (object old-x old-y new-x new-y) (setf (get-object new-x new-y) object) (setf (get-object old-x old-y) nil) (setf (get-cycle object) (mod (1+ (get-cycle object)) 4)) (push (list (identify-inanimate-object-only object) *clock* new-x new-y) *inanimate-object-motions*)) (compute-move (cycle x y) #+Genera (declare (values new-x new-y move-legal?)) (ecase cycle (0 (values (fix1+ x) y (< x *world-diameter*))) (1 (values x (fix1- y) (> y 0))) (2 (values (fix1- x) y (> x 0))) (3 (values x (fix1+ y) (< y *world-diameter*)))))) (dotimes (x *world-diameter*) (declare (fixnum x)) (dotimes (y *world-diameter*) (declare (fixnum y)) (let ((object (get-object x y))) (when (and object ;; &&& I'd like to fix this to call random AFTER we check whether we can move the object, ;; but I can't do that until I re-evolve where all the objects are in a run for the demo animation. ;; (This would run a whole lot faster if we didn't call random every loop, but only when necessary.) ;; It would also mean that all simulations after that point would pick different actions, depending on ;; how the world ran that time, hence things might diverge on the action front anyway. (And it's ;; for sure that runs before & after this change would pick different actions, since we wouldn't be ;; calling RANDOM 4 times (here---see COUNT-OBJECTS-IN-WORLD) in between each call to it to pick ;; an action. *sigh* (fix< (random 200000) 1000) (not (and (fix= x (fix1+ *hp-x*)) (fix= y (fix+ 2 *hp-y*)) *hgr*)) (get-movable object)) (multiple-value-bind (new-x new-y legal?) (compute-move (get-cycle object) x y) (when (and legal? ; Won't move it past the end of the world. (not (get-object new-x new-y))) ; Won't land on some other object. (do-move object x y new-x new-y))))))))) (defun CLOCK-TICK () (clock-tick-maybe-move-objects) (when (and *hcl* (fix= *clock* *grip-expiration*)) (ungrasp)) (setf *clock* (fix1+ *clock*))) ;;; +++ Debugging function only from here to the end of the page. ;;; OBJECT-1 should be the left object (from inspection of the ancient code's init-world stuff). ;;; Restores ONLY the microworld state, not the schema state. ;;; [This would be better written as SCHEMA::(...), but Harlequin apparently ;;; chokes if a newline or a paren follows a colon. So do it the hard way...] (defun SCHEMA::RESTORE-EYEHAND-ONLY (snapshot-spec) (let ((snapshot (schema::snapshot-from-spec snapshot-spec))) (schema::with-snapshot-destructuring snapshot schema::schema-system-version schema::timestamp schema::clock-tick schema::metering-state schema::schema-state schema::random-state schema::comment ; Ignored. (eyehand::safe-restore-eyehand-state schema::microworld-state)))) (defun TICKER-RESET () (schema::restore-eyehand-only 0) (setf *inanimate-object-motions* nil) (values)) (defun COUNT-OBJECTS-IN-WORLD () (let ((count 0)) (dotimes (x *world-diameter*) (dotimes (y *world-diameter*) (when (get-object x y) (incf count)))) count)) (defun IDENTIFY-OBJECTS-IN-WORLD () (terpri) (loop for y from (1- *world-diameter*) downto 0 do (loop for x from 0 below *world-diameter* do (let ((object (get-object x y))) (if object (format t "~9A" (identify-object object)) (format t "........ ")))) (terpri)) (values)) (defun TEST-TICKER () (loop for counter from 0 until *inanimate-object-motions* do (clock-tick) finally (return counter))) (defun SHOW-SOME-TICKS (&optional (n 100)) (identify-objects-in-world) (loop with initial-length = (length *inanimate-object-motions*) until (zerop n) do (clock-tick) (let ((new-length (length *inanimate-object-motions*))) (unless (= initial-length new-length) (setf initial-length new-length) (decf n) (identify-objects-in-world)))) (values)) ;;;; Random number generator seed saving function ;;;; NEW-INITIAL-RANDOM-STATE ;;; This function writes a new random-state seed ;;; to the disk file "state.dat" . ;;; This seed will be used the next time the world is initialized. (defun NEW-INITIAL-RANDOM-STATE (&optional (filename "state.dat")) (with-open-file (out-file filename :direction :output :if-exists :supersede) (print (make-random-state t) out-file) (terpri out-file)) 'new-initial-random-state-written-to-disk) ;;;; Microworld initialization function INIT-WORLD. ;;; This function loads the current random seed from disk and resets ;;; all the world variables appropriately. ;;; The hand is placed at body relative coordinate (1,0) which is ;;; microspace relative coordinate (3,2). ;;; The center of the visual field is oriented at body relative ;;; coordinate (1,1) which is microspace relative coordinate (3,3). ;;; The hand is left open. ;;; The world is recreated from scratch, with the hand, body and two ;;; objects placed in the appropriate places, and the cycle values ;;; (used by clock-tick for randomly moving objects) reset. ;;; Finally, it resets and returns the value of *CLOCK*. (defun INIT-WORLD (&optional (filename "state.dat")) ; (with-open-file (in-file filename ; :direction :input ; :if-does-not-exist :error) ; (setf *random-state* (read in-file nil nil nil))) filename ; Ignored for now. --- Foner. (setf *grip-expiration* -1 *hp-x* 1 *hp-y* 0 *vp-x* 1 *vp-y* 1 *hcl* nil *hgr* nil *world* (make-array (list *world-diameter* *world-diameter*) ; &&& Um, why do we bother setting it in a DEFVAR and here? :initial-element nil) (get-object (fix+ 2 *body-x*) (fix+ 2 *body-y*)) *body* ; E.g., at 3,1. Why it doesn't do it more directly, I dunno. (get-object (fix+ 2 *hp-x*) (fix+ 2 *hp-y*)) *hand* ; E.g., at 3,2. (get-object 1 5) *object-1* ; cycle 0 moves right (get-cycle *object-1*) 0 (get-object 5 5) *object-2* ; cycle 1 moves down (get-cycle *object-2*) 1 *clock* 0)) ;;;; Saving and restoring the state of the microworld. (defparameter *EYEHAND-STATE-SCALARS* '( *clock* *grip-expiration* *hp-x* *hp-y* *vp-x* *vp-y* *hcl* *hgr*)) (defparameter *EYEHAND-STATE-NONSCALARS* '( *world* *body* *hand* *object-1* *object-2*)) (defparameter *EYEHAND-STATE-NONSCALAR-COPIERS* '( (*world* safe-copy-world) (*body* safe-copy-world-object) (*hand* safe-copy-world-object) (*object-1* safe-copy-world-object) (*object-2* safe-copy-world-object))) (defvar *EYEHAND-STATE-VARIABLES* (append *eyehand-state-scalars* *eyehand-state-nonscalars*)) (defun SAFE-COPY-WORLD-OBJECT (o) (let ((new (copy-world-object o))) (setf (world-object-visual new) (copy-seq (world-object-visual new)) (world-object-tactile new) (copy-seq (world-object-tactile new)) (world-object-taste new) (copy-seq (world-object-taste new))) new)) (defun SAFE-COPY-WORLD (w) ;; We assume that the array is 2D. If I wanted to be Genera-specific, I could ;; use the SYS:ARRAY-REGISTER-1D declaration to force them into a 1D framework. ;; Alternately, if I really cared about generality, I could use a displaced array to do ;; the same thing. But this is a tiny array, and known to be 2D, so what the hell... (let* ((old w) (new (make-array (array-dimensions old) :element-type (array-element-type old)))) (dotimes (x (array-dimension old 0)) (dotimes (y (array-dimension old 1)) (let ((elt (aref old x y))) (when elt (setf (aref new x y) (safe-copy-world-object elt)))))) new)) (defun EYEHAND-COPIER-FROM-SYMBOL (symbol) (second (assoc symbol *eyehand-state-nonscalar-copiers*))) (defun SAFE-COPY-EYEHAND-STATE () (append (loop for scalar-symbol in *eyehand-state-scalars* collect (symbol-value scalar-symbol)) (loop for nonscalar-symbol in *eyehand-state-nonscalars* for copier = (eyehand-copier-from-symbol nonscalar-symbol) collect (funcall copier (symbol-value nonscalar-symbol))))) (defun SAFE-RESTORE-EYEHAND-STATE (snapshot) ; This is NOT an entire snapshot, but only the MICROWORLD-STATE part of it! (loop for state-symbol in *eyehand-state-variables* for item in snapshot for item-is-nonscalar? = (member state-symbol *eyehand-state-nonscalars*) for restore-fn = (and item-is-nonscalar? (eyehand-copier-from-symbol state-symbol)) do (set state-symbol (if item-is-nonscalar? (funcall restore-fn item) item))) (values)) ;;; End of file.