/* EDEN_CORE.P */ section $-eden => eden, say, sentence, inventory, smell, retina, energy, bug_message, bug_using_ved, bug_view, bug_view_on, bug_view_off; /* SPECIFICATION ------------- This is the main module defining Eden. It has to be loaded by EDEN.P. For details of Eden itself, see HELP EDEN. The specifications given below assume you have read that. Running Eden: ============= PUBLIC eden( ): 'eden' starts Eden running, putting a bug into a world and setting it off. The arguments are eden( , , ) eden( ) where stands for any number (including none) of the following arguments, in any order: "noved" "batch" "prolog" [% "save", file, proc, proc, ... %] and must be strings, denoting files. The bug-file must be a file of Pop-11 code, and its extension defaults to .P. The world-file contains a saved world, as saved by the Ved 'saveworld' command, and its extension defaults to .W. If a file is specified without a directory, Eden looks first in the current directory. If it can't find the file there, it then searches the library files, as defined by 'popuseslist'. See HELP POPUSESLIST for more details. This assumes Eden and the standard worlds and bugs will have been set up as libraries, which I hope will be true on most systems. The "noved", "batch" and "prolog" options are Pop-11 words. The save option is a list whose first element should be the word "save", whose second element should be a filename, and whose remaining elements should be procedures. Example calls of eden are: eden( "noved" ); eden( 'MYBUG', 'tw1', [ save mycases ^energy ^retina ], "batch" ); eden( 'manual_bug', 'MyWorld', [% "save", 'CASES', retina, smell, energy %], "noved" ); The "prolog" option should only be used by Prolog Eden, when calling 'eden' from Prolog. It isn't for the user. How to avoid repeating filenames -------------------------------- If the world and bug you want to eden are exactly the same as last time, you can omit them. You can still specify the other options to eden(), for instance: eden(); eden( "batch" ); eden( [% "save", 'CASES', retina, smell, energy %], "noved" ); Saving cases ------------ By using the save option, you can direct that perceptions and actions are to be stored in a file. You can then read this back later, and use it (for example) for training a neural net. Example options are: [ save cases ^energy ^retina ^smell ] [ save fred ] [ save cases ^myproc ^energy ] In each of these, the second element is a filename. It can be either a word or a string. The remaining elements are procedure names. Each procedure should return at least one result. The procedures can be Eden's perceptual procedures, 'smell', 'energy', etc., or can be your own. When running your bug in save mode, Eden starts each cycle by invoking your 'think' procedure, and noting the action returned. It then calls each procedure in your save-list, and makes a list of the results. It puts the action at the end of this list, and saves the whole list in a your file. So if your save option was this [ save cases ^energy ^retina ^smell ] then the first list saved might be [% 500, R, "forward", "forward" %] where 500 is the energy, R stands for the retina (it will be saved as an array, of course), and the last two elements are the smell and the bug's action. These lists would all be saved in the file CASES. Everything else works as without the save option; the only difference you may notice is that the simulation will be slightly slower. The saved cases can be read back with library STOW, see HELP STOW. Briefly, suppose you have a case file called CASES. You can ``open'' this by doing unstow_from( 'CASES' ) -> r; This makes 'r' into a ``repeater'' --- that is a procedure that, on each call, returns the next item in sequence. So if your save option had been [ save cases ^energy ^retina ^smell ] then each call of r would give you a list whose first element was the energy, whose second was the retina, and whose third was the smell. As mentioned above, the first of these might be [% 500, R, "forward", "forward" %] Running without Ved ------------------- To run outside Ved, give the "noved" option to eden(). Eden will run, displaying the world and status information for each cycle in teletype mode, as a sequence of lines. The prompts are the same, but you won't be able to edit sentences passed to the listener. Examples are: eden( "noved" ); eden( 'talk_bug', 'tw3', "noved" ); Batch mode ---------- In normal use, whether Ved or non-Ved mode, Eden interacts with the user, prompting for the number of cycles to run next. This would make it tedious to run a bug which needs to run through several hundred or even thousand worlds. By giving the "batch" option, you can specify that Eden is to run lives until bugdead finally returns "stop", without any prompting or display of the world. In batch mode, Eden prints three lines to the standard output for each life, like this: Starting life 1. Life 1 succeeded on energy 494. Result from bugdead: rerun. Sections -------- On exit, 'eden' resets the current section to 'pop_section': see the implementation notes. The senses: =========== PUBLIC retina(): Returns Bug's retina, as a 2-D array with origin (1,1). Subscript this thus: retina()(X,Y) The result will be a character representing the object seen, ` ` if none. Bug is in the position (3,2), and does not see himself. PUBLIC smell(): The direction of the food. This will be one of the words "forward", "right", "back", "left", "here", "carried". PUBLIC sentence(): The last sentence ``heard'' by Bug. [] if none, otherwise a list of characters. PUBLIC inventory(): The contents of Bug's inventory. A character representing an object: ` ` if none. PUBLIC energy(): Bug's current energy. This will always be between 0 and its initial energy, inclusive. Special output: =============== PUBLIC bug_using_ved(): True if Ved is being used, i.e. if 'eden' was called without the "noved" option. False otherwise. PUBLIC bug_message( thing ): Displays 'thing' as a status message. In Ved mode, this is displayed on the status line by vedputmessage, otherwise as a line of text terminated by a newline. 'thing' need not be a string, the routine will convert it to one. The view window: ================ The bug_view routine allows you to write output that will get sent to a separate Ved window. There are also routines for turning view output on and off. PUBLIC bug_view expr: bug_view is a syntax word which must be followed by an expression. This expression should be a call to an output routine. In Ved mode, before calling the routine, bug_view opens a new Ved window for a buffer called VIEW, and gives it the bottom half of the screen, leaving the bug in the top half. bug_view then arranges for the output of this routine to be sent to the next location in the window. Once bug_view has finished, the window remains open until the end of the run. Subsequent calls to bug_view will write their output at the end of the same window. In non-Ved mode, bug_view proc() is equivalent to proc(). Examples are: bug_view pr('Demonstrating my new bug\n'); bug_view printf('Smell was: %p\n', [% smell() %] ); PUBLIC bug_view_on(): PUBLIC bug_view_off(): The routines bug_view_on() and bug_view_off() turn view on and off. By default, 'eden' turns it on. If it's turned off, calls to bug_view will have no effect. This is useful if you want to leave bug_view statements in, but disable them on some runs. */ /* IMPLEMENTATION -------------- Name ---- I name this file EDEN so that it has an obvious name. However, I want it to be separate from the main body of Eden: that's why the latter is called EDEN_CORE. Documentation and other dependencies ------------------------------------ The main HELP file for this program is HELP EDEN. Please make sure that you update it when you change anything in Eden. This applies, amongst other things, to the specification of routines such as drop(), which are used by people writing their own objects. It also applies, more obviously, to the use of eden(), to saving cases, to Ved versus non-Ved modes, and to the simulator prompts. A few other HELP files also refer to HELP EDEN, HELP RETINA in particular. Various modules, particularly WORLDS.P, assume it when specifying things such as the format of worlds to be read by Ved. AWM.P assumes in awm_merge_retina that the retina is as in this version of Eden. This means that changes to Eden may invalidate other HELP files, and the specification comments in some of the other libraries. Before making any changes therefore, it's best to read all the HELP files and library specifications, to gain an overview of what is mentioned where. The simulator ------------- The top-level routine is 'eden'. This gets the options and sets some global variables accordingly, and then calls 'simulate'. If in Ved mode, it may call 'simulate' to be run inside Ved. 'simulate' is the main simulation routine: it loads the world and bug, and then calls 'life' repeatedly to handle each life, using 'bugdead' to indicate whether to stop. 'life' calls 'cycle' to handle each simulation cycle. These cycles have the form update bug perceptions call brain update world according to result The main world-updating routine is 'resolve'. This calls the object-writer's own routines, as described in HELP EDEN. It also calls routines defined lower down here, such as 'drop', once the world is definitely to be changed. Sections -------- I assume that, since most bug-writers will be novices, they won't know about sections, and so their code will be in the top-level (pop_section). Hence I've ensured that when a bug's code is loaded, it's compiled in that section, and that brain procedures are fetched from it. This is done inside 'simulate'. Objects are assumed to be written in the same section as this one, $-eden. This gives easy access to useful routines such as drop() and fed(), and obviates the need for them to be made public. Object writers will know more about Pop, and should be able to handle sections, at least to the extent of treating them as ``magic brackets''. Users of Prolog Eden will write their bug brains to be in Prolog's equivalent of pop_section. Eden doesn't call these directly, but via interface routines defined in EDEN_CORE.PL. These interface routines are put into section $-eden, and hence 'simulate' fetches them from there. When 'eden' returns, it switches section back to pop_section. I have found that if I don't do this, then after calling Prolog eden, it gets set to $-eden. I don't know why. It is necessary to change the section to pop_section before calling the brain (and possibly start_thinking and bugdead). If this isn't done, then if they use ? or ?? in the matcher, it will assign to variables in $-eden. Portability ----------- One definite non-portability in this module is the variable 'current_directory' below. Non-VMS users will need to change it. The only other thing that may give problems is the file-handling code in LOAD_FILES.P and ADD_FILE_DEFAULTS.P. Programming style ----------------- I like writing my programs top-down, with the highest-level procedures first. Unfortunately, this conflicts with Pop's automatic declaration feature, and causes a fountain of undeclared variable messages, as the compiler encounters a call to yet another undefined procedure. This is why several procedures are declared first as vars ; /*forward*/ See also the file STYLE for programming style. */ needs worlds; needs utils; needs fault; needs vedreadlinechars; needs stow; needs draw_lines; needs chars_to_items; needs load_file; needs smatch; vars current_directory = '[]'; /* The designation for the current directory. */ vars world; /* The current world. */ vars action; /* The bug's last action. */ vars mode; /* Whether to run in slow, fast, or batch modes. */ vars save_cases; /* true if cases to be saved. */ vars what_to_save; /* only defined if save_cases. A list specifying which perceptions to save. */ vars where_to_save; /* only defined if save_cases. A filename specifying where to save to. */ vars language; /* which language the bug is in. Currently deals with "pop" and "prolog". */ vars brain; /* the procedure used as the current brain. */ vars start_brain; /* the procedure used as the current brain-start-up. */ vars brain_dead; /* the procedure used as the current bugdead. */ vars save_stream; /* Defined if save_cases. The consumer for saved cases. */ vars view_opened; /* Whether the view file has been opened in the current run. */ vars no_view; /* True iff view command is to be ignored. */ vars use_ved; /* Whether Eden is to be run from inside Ved or not. */ vars stop; /* Whether bugdead wants to stop, or to continue with another life. */ vars succeeded; /* Whether the bug succeeded in finding food. */ vars killed; /* Set false at the start of each life; becomes true when the bug dies. */ /* Top-level procedure. -------------------- The main procedure for starting Eden is eden. This is designed to be called directly from Pop-11, or encapsulated in Prolog. */ vars set_default_parameters, eden_;/*forward*/ define eden(); lvars bug_file, world_file; true -> use_ved; false -> save_cases; "pop" -> language; "slow" -> mode; set_default_parameters(); /* Takes any options off the stack, and sets globals accordingly. */ if stacklength() >= 2 then if ().dup.isstring or ().dup.isword then () -> world_file; () -> bug_file; endif else "noworld" -> world_file; "nobug" -> bug_file; endif; /* Takes the filenames, if present. */ eden_( bug_file, world_file ); pop_section -> current_section; enddefine; /* set_default_parameters: Reads all the options from the stack, and assigns them to the global variables. Everything else is left on the stack. */ define set_default_parameters(); lvars top; vars what, where; if stacklength() >= 1 then () -> top; switchon top case = "noved" then false -> use_ved; set_default_parameters(); case = "batch" then false -> use_ved; "batch" -> mode; set_default_parameters(); case matches [ save ? ^ !where ?? ^ !what ] then true -> save_cases; what -> what_to_save; where >< '' -> where_to_save; set_default_parameters(); case = "prolog" then "prolog" -> language; set_default_parameters(); else top; endswitchon endif; enddefine; vars simulate, load_world, call_in_section;/*forward*/ /* eden_( bug_file, world_file ): This is called by 'eden' once it has taken off the options. bug_file will be either "nobug" or a filename. world_file will be either "noworld" or a filename. The global variables denoting options will be set as follows: use_ved: true or false. mode: "slow" or "batch". language: "pop" or "prolog". save_cases: true or false. where_to_save: only defined if save_cases. Is the filename in the save-option. what_to_save: only defined if save_cases. Is the list of procedures occurring in the save-option after the filename. */ define eden_( bug_file, world_file ); lvars bug_file, world_file; /* Load bug code. */ unless bug_file = "nobug" then /* 1) Get the full filename. Using 'identfn' as an argument causes it to be returned as load_file's result. */ if language = "pop" then load_file( bug_file, '.p', identfn, [ ^current_directory ]<>popuseslist ) -> bug_file; .erase; elseif language = "prolog" then load_file( bug_file, '.pl', identfn, [ ^current_directory ]<>prologliblist ) -> bug_file; .erase; endif; /* 2) Error if file not found. */ if bug_file = false then mishap( 'eden_: bug file not found', [%bug_file%] ); endif; /* 3) Compile the full filename, in pop_section. */ if language = "pop" then call_in_section( pop_section, compile(%bug_file%) ); elseif language = "prolog" then call_in_section( pop_section, prolog_compile(%bug_file%) ); endif; endunless; /* Load world. */ unless world_file = "noworld" then load_world( world_file ); endunless; /* Set consumer for saved cases. */ if save_cases then stow_to( where_to_save ) -> save_stream; endif; /* By default, viewing is on. */ false -> no_view; /* Run Eden. */ if use_ved then vedscreenlength -> vedstartwindow; false -> vedautowrite; false -> view_opened; vedobey( 'bug', simulate ); else simulate(); endif; /* Close save-file. */ if save_cases then save_stream( termin ); endif; enddefine; /* Eden. ----- */ vars prolog_bugdead, prolog_think, prolog_start_thinking; /* Defined in EDEN_CORE.PL */ vars from_section, save_case, reset_world, replace_world, set_brain, life, retina, sentence, smell, inventory, energy;/*forward*/ /* simulate(): This is the main simulator procedure, called by eden_. On entry, the following global variables will be set: world: the current world. save_stream: the consumer along which cases are saved. This is not used directly. Instead, if cases are to be saved, a new ``brain'' is built which thinks as before, but also writes its perceptions and actions along this consumer. view_opened: false. Various Ved variables. The bug file will have been compiled in pop_section, hopefully defining 'bugdead', 'start_thinking' and 'think'. The world file will have been loaded into 'world', and its objects-file will have been compiled in section $-eden (i.e. this one). */ define simulate(); lvars disposition, i, proc, no_of_lives; vars worldfile; setpop -> interrupt; /* Open bug window and clear any rubbish left in the file. */ if use_ved then vedselect( 'bug' ); ved_clear(); endif; /* Get brain. */ if language = "prolog" then prolog_think elseif language = "pop" then from_section(pop_section,"think") else FAULT( 'simulate: unrecognised language', [% language %] ) endif -> brain; /* Get start-brain routine. */ if language = "prolog" then prolog_start_thinking elseif language = "pop" then from_section(pop_section,"start_thinking") endif -> start_brain; /* Get bugdead routine. */ if language = "prolog" then prolog_bugdead elseif language = "pop" then from_section(pop_section,"bugdead") endif -> brain_dead; /* If saving cases, then build a new procedure which saves the results of the procs specified in the save option. We do this by building a new procedure which is the composition of all these procs, together with something which returns the action. */ if save_cases then if language = "prolog" then [% retina, sentence, smell, inventory, energy %] -> what_to_save; endif; what_to_save(1) -> proc; for i from 2 to length(what_to_save) do proc <> what_to_save(i) -> proc; endfor; procedure(p); lvars p; p() -> action; save_case( proc<>identfn(%action%) ); action endprocedure(% brain %) -> brain; endif; /* Reset world and put brain into it. */ reset_world(); set_brain( brain ); /* Needed below to determine whether to exit. */ false -> stop; /* Call start_thinking. */ call_in_section( pop_section, start_brain ); /* Run lives repeatedly. 'disposition' is the result returned by bugdead. */ 0 -> no_of_lives; until stop do 1 + no_of_lives -> no_of_lives; life( no_of_lives ) -> disposition; if disposition = "stop" then true -> stop; elseif disposition = "rerun" then reset_world(); elseif disposition matches [ rerun ? ^ !worldfile ] then replace_world( worldfile><'' ) endif; enduntil; enddefine; /* Individual lives and cycles. ---------------------------- */ vars initialise_display, say, set_sentence, bug_message, cycle, show_info, show_world, ask_number_of_cycles;/*forward*/ vars display_updated; /* This is declared later, and its use here is messy. */ /* life( number ): This procedure runs one life, and is called from simulate(). number is the number of the current life, starting from 1, and is used only for display. */ define life( number ); lvars number; lvars result, cycles_left; /* 'killed' is set by kill() and fed() when the bug dies. */ false -> killed; if mode = "batch" do printf( 'Starting life %p.\n', [%number%] ) else initialise_display(); show_info(); show_world(); endif; /* Ensure that in batch mode, Eden never stops and gives the continue prompt. */ if mode = "batch" then 1000000 else 0 endif -> cycles_left; until killed do /* Set heard and said sentences to null on each cycle. */ set_sentence( [] ); say( [] ); /* This 'if' should always do nothing in batch mode. */ if cycles_left = 0 then if mode = "fast" then show_info(); false -> display_updated; /* This forces show_world to display the new world-state. So after a sequence of fast actions has finished, we see the latest state of the world. */ show_world(); endif; ask_number_of_cycles() -> cycles_left; if mode = "fast" then bug_message( 'Entering fast mode' ); endif; /* NB. This may cause an exit from 'simulate' if the user so requests. It also deals with sentence-reading. */ endif; cycle(); cycles_left - 1 -> cycles_left; if mode = "slow" then show_info(); show_world(); elseif mode = "fast" then bug_message( 'Fast mode: energy '> result; if mode = "batch" then printf( 'Life %p %p on energy %p.\n', [% number, if succeeded then "succeeded" else "failed" endif, energy() %] ); printf( 'Result from bugdead: %p.\n\n', [% result %] ); endif; result; enddefine; vars kill, set_energy, do_think, resolve, update_perceptions;/*forward*/ /* cycle(): Runs one cycle of Eden, drawing on the screen as it does so. */ define cycle(); update_perceptions(); call_in_section(pop_section,do_think) -> action; resolve(); /* May want later to have a routine here that moves any animate but non-Bug objects. */ set_energy( energy()-1 ); if energy()<1 then kill(); endif; enddefine; vars ask_continue;/*forward*/ /* ask_number_of_cycles(): Gives the continue prompt, reads and checks the reply. If the user types l, reads the sentence; if he types p, reads and obeys the Pop-11 code. */ define ask_number_of_cycles(); lvars reply; vars what, rest, cycles; while ( ask_continue() ->> reply ) matches [ ? ^ !what ?? ^ !rest ] and ( what="l" or what="p" or what="r" ) do if what = "l" then set_sentence( rest(1) ); elseif what="p" then call_in_section( pop_section, popval(%chars_to_items(rest(1))%) ); elseif what="r" then if use_ved then vedrefresh(); endif; endif; endwhile; if reply = [n] then bug_message( 'Exiting Eden' ); exitfrom( simulate ); elseif reply = [y] then "slow" -> mode; 1; elseif reply matches [ f ? ^ !cycles ] then "fast" -> mode; cycles; elseif reply matches [ s ? ^ !cycles ] then "slow" -> mode; cycles; else FAULT( 'ask_number_of_cycles: bad reply', [% reply %] ); endif; enddefine; /* Actions. -------- */ vars signal_error, try_forward, try_back, try_left, try_right, try_grab, try_drop, try_wait, try_use;/*forward*/ /* resolve(): This takes the Bug's last action and updates the world accordingly. Illegal actions are reported but don't terminate the simulation. */ define resolve(); switchon action case = "forward" then try_forward() case = "back" then try_back() case = "left" then try_left() case = "right" then try_right() case = "grab" then try_grab() case = "drop" then try_drop() case = "wait" then try_wait() case = "use" then try_use() else bug_message( 'Illegal action: '> resultin; if resultin/="stop" then one_forward( bug_xW(), bug_yW() ) -> next_yW -> next_xW; object_proc(world(next_xW,next_yW))("forward_ext") -> resultout; if resultin="continue" and resultout="continue" then place_bug_at( next_xW, next_yW ) elseif resultout="fail" then object_proc(object_at_bug())("wait_ext") -> resultout; endif endif; enddefine; vars one_back;/*forward*/ /* try_back(): Update the world after a "back" action. */ define try_back(); lvars resultin,resultout,next_xW,next_yW; object_proc(inventory())("back_held") -> resultin; if resultin/="stop" then one_back( bug_xW(), bug_yW() ) -> next_yW -> next_xW; object_proc(world(next_xW,next_yW))("back_ext") -> resultout; if resultin="continue" and resultout="continue" then place_bug_at( next_xW, next_yW ) elseif resultout="fail" then object_proc(object_at_bug())("wait_ext") -> resultout; endif endif; enddefine; /* try_left(): Update the world after a "left" action. */ define try_left(); lvars resultin,resultout; object_proc(inventory())("left_held") -> resultin; if resultin/="stop" then object_proc(object_at_bug())("left_ext") -> resultout; if resultin="continue" and resultout="continue" then left(); endif; endif; enddefine; /* try_right(): Update the world after a "right" action. */ define try_right(); lvars resultin,resultout; object_proc(inventory())("right_held") -> resultin; if resultin/="stop" then object_proc(object_at_bug())("right_ext") -> resultout; if resultin="continue" and resultout="continue" then right(); endif; endif; enddefine; vars grab;/*forward*/ /* try_grab(): Update the world after a "grab" action. */ define try_grab(); lvars resultin,resultout; object_proc(inventory())("grab_held") -> resultin; if resultin/="stop" then object_proc(object_at_bug())("grab_ext") -> resultout; if resultin="continue" and resultout="continue" then grab(); endif; endif; enddefine; vars drop;/*forward*/ /* try_drop(): Update the world after a "drop" action. */ define try_drop(); lvars resultin,resultout; object_proc(inventory())("drop_held") -> resultin; if resultin/="stop" then object_proc(object_at_bug())("drop_ext") -> resultout; if resultin="continue" and resultout="continue" then drop(); endif; endif; enddefine; /* try_wait(): Update the world after a "wait" action. */ define try_wait(); lvars resultin,resultout; object_proc(inventory())("wait_held") -> resultin; if resultin /= "stop" then object_proc(object_at_bug())("wait_ext") -> resultout; endif; enddefine; /* try_use(): Update the world after a "use" action. */ define try_use(); lvars resultin,resultout; object_proc(inventory())("use_held") -> resultin; if resultin /= "stop" then object_proc(object_at_bug())("use_ext") -> resultout; endif; enddefine; vars objects; /* This is the table of object-properties, to be defined by the object-writer. */ /* object_proc(c): The object-procedure for the object whose symbol is character c. */ define object_proc(c); lvars c; objects(c)(2); enddefine; /* object_name(c): The text name for the object whose symbol is character c. */ define object_name(c); lvars c; objects(c)(1); enddefine; /* Updating the world and display. ------------------------------- Everything above this point has to see the world as an abstract machine which is updated by the routines place_bug_at( xW, yW ) place_object_at( xW, yW, char ) left() right() drop() grab() set_energy() set_sentence() update_perceptions() update_smell() say() kill() fed() The reason for this is that these are the routines which may affect the display. They are allowed to do so immediately: if not, the changes they made must be depicted by the following call of show_world or show_info. In the current implementation, the world- and bug-drawing routines update the display immediately, if using Ved and in slow mode. Everything else waits for show_world or show_info. */ vars display_updated; /* initialise_display(): */ define initialise_display(); false -> display_updated; if use_ved then ved_clear(); vedsetscreen(''); endif; enddefine; vars draw_bug;/*forward*/ /* place_bug_at( xW, yW ): Move the bug to xW,yW. Display the contents of that location before and after the move, if using Ved and slow. */ define place_bug_at( xW, yW ); lvars xW, yW; if mode="slow" and use_ved then draw( bug_xW(), bug_yW(), object_at_bug(), "check" ); true -> display_updated; endif; bw_move_bug_to( world, xW, yW ); if mode="slow" and use_ved then draw_bug(); true -> display_updated; endif; enddefine; /* place_object_at( xW, yW, char ): Place char at xW,yW. Display the contents of that location after the move, if using Ved. */ define place_object_at( xW, yW, char ); lvars xW, yW, char; if mode="slow" and use_ved then draw( xW, yW, char, "nocheck" ); true -> display_updated; endif; char -> world( xW, yW ); enddefine; /* left(): */ define left(); bw_left( world ); enddefine; /* right(): */ define right(); bw_right( world ); enddefine; /* grab(): */ define grab(); bw_grab( world ); enddefine; /* drop(): */ define drop(); bw_drop( world ); enddefine; define update_perceptions(); bw_update_perceptions( world ); enddefine; define update_smell(); bw_update_smell( world ); enddefine; /* set_sentence( sentence ): Updates the last sentence ``heard'' to sentence. */ define set_sentence( sentence ); lvars sentence; bw_set_sentence( world, sentence ); enddefine; /* set_energy( e ): Updates Bug's energy to e. */ define set_energy( e ); lvars e; bw_set_energy( world, e ); enddefine; define say( sentence ); lvars sentence; bw_set_reply( world, sentence ); enddefine; /* set_inventory( char ): Updates Bug's inventory to char. */ define set_inventory( char ); bw_set_inventory(world,char); enddefine; /* Display. -------- This section assumes things about the screen layout. Horizontally, all output starts at column 1 and carries on as far as possible: there are no marginal annotations or other effects. Vertically, the bug window is divided as follows: First two lines : the bug status information. Third line : what the bug last heard. Fourth line : what the bug last said. Fifth line : blank. Sixth line : top line of world. */ constant info_line_1 = 1; constant info_line_2 = 2; constant heard_line = 3; constant said_line = 4; constant world_line_1= 5; vars redraw_display;/*forward*/ define show_world(); unless use_ved and display_updated then redraw_display(); endunless; true -> display_updated; enddefine; vars world_width, world_height;/*forward*/ define redraw_display(); lvars i, j, char; if not( use_ved ) then nl(1) endif; for j from world_height()-1 by -1 to 0 do for i from 0 to world_width()-1 do if i=bug_xW() and j=bug_yW() then `B` else world( i, j ) endif -> char; if use_ved then draw( i, j, char, "nocheck" ) else cucharout( char ) endif; endfor; if not( use_ved ) then nl(1) endif; endfor; if not( use_ved ) then nl(1) endif; if use_ved then draw_bug() endif; ;;; The bug has already been displayed, but this will ensure that ;;; it is in view, since draw_bug does so. enddefine; vars reply;/*forward*/ vars direction;/*forward*/ define show_info(); lvars p1, p2, p3, p4; update_smell(); /* So the food bearing accords with the latest changes in the world. */ if use_ved then vedformat_print(% info_line_1 %) -> p1; vedformat_print(% info_line_2 %) -> p2; else format_print ->> p1 -> p2; endif; p1( 'Action: ~8A Facing: ~8A Position: ~8AEnergy: ~8A', [% action, direction(), '('><','><')', energy() %] ); if not(use_ved) then nl(1); endif; p2( 'Inventory: ~8A Food: ~8A Here: ~8A', [% object_name(inventory()), smell(), object_name(object_at_bug()) %] ); if not(use_ved) then nl(1); endif; if use_ved then vedformat_print(% heard_line %) -> p3; vedformat_print(% said_line %) -> p4; else format_print ->> p3 -> p4; endif; p3( '~{~C~}', [%sentence()%] ); if not(use_ved) then nl(1); endif; p4( '~{~A ~}', [%reply()%] ); if not(use_ved) then nl(1); endif; enddefine; /* draw_bug(): Display the bug, and ensure it's in view. Precondition: use_ved. */ define draw_bug(); draw( bug_xW(), bug_yW(), `B`, "check" ); ;;; The "check" argument ensures the bug is in view. enddefine; /* draw( xW, yW, char, check ): Update the VED display with char, placing it in the position appropriate to world co-ordinates (xW,yW). If check = "check", then ensure that this position is in the visible window. Precondition: use_ved. */ define draw( xW, yW, char, check ); lvars xW, yW, char, check; vedjumpto( (world_line_1-1) + world_height()-yW, xW+1 ); char -> vedcurrentchar(); if check = "check" then vedcheck() endif; enddefine; /* Bug's perceptions. ------------------ The routines in this section access Bug's perceptions. They can all be called by authors of bugs, and are all exported. */ define inventory(); world.bw_inventory; enddefine; define smell(); world.bw_smell; enddefine; define retina(); world.bw_retina; enddefine; define energy(); world.bw_energy; enddefine; define sentence(); world.bw_sentence; enddefine; /* Simulation primitives. ---------------------- The object-writer can call: bug_xW() bug_yW() forwardvector() rightvector() direction() object_at_bug() inventory() energy() initial_energy() one_forward() one_back() one_left() one_right() place_object_at() drop() grab() fed() kill() as the most useful. See HELP EDEN, section on writing your own objects. If you update this code, please make sure you keep HELP EDEN in step. At the moment, these routines rely on calls of FAULT inside WORLDS.P for their error-detection, in errors such as trying to drop when the inventory is empty. */ /* reset_world(): Sets up the world. */ define reset_world(); world.bw_reset; "none" -> action; enddefine; /* replace_world( world_file ): Sets up a replacement world, by loading it, and then setting the brain and resetting the world. */ define replace_world( world_file ); lvars world_file; load_world( world_file ); set_brain( brain ); world.bw_reset; "none" -> action; enddefine; /* load_world( world_file ): Loads a new world. */ define load_world( world_file ); lvars world_file; lvars objects_file; load_file( world_file, '.w', identfn, [ ^current_directory ]<>popuseslist ) -> world_file; .erase; if world_file = false then mishap( 'eden_: world file not found', [%world_file%] ); endif; worldfile_to_world( world_file ) -> world; bw_objects_file( world ) -> objects_file; load_file( objects_file, '.p', identfn, [ ^current_directory ]<>popuseslist ) -> objects_file; .erase; if objects_file = false then mishap( 'eden_: objects file not found', [%objects_file%] ); endif; call_in_section( section_subsect( "eden", pop_section, false ), compile(%objects_file%) ); enddefine; /* do_think(): Invokes the brain in the current world. You must ensure the perceptions are up-to-date first. */ define do_think(); world.bw_think; enddefine; /* set_brain( brain ): Sets the brain in the current world to brain. This must be a 'think' procedure of no arguments and one result, an action. */ define set_brain( brain ); lvars brain; bw_set_brain( world, brain ); enddefine; /* kill(): Notes that the bug has been killed, by some means _other_ than finding food. You must call this whenever it is killed, since the simulator uses it to tell when to stop and call 'bugdead'. */ define kill(); true -> killed; false -> succeeded; enddefine; /* fed(): Notes that the bug has been killed, by finding food. You must call this whenever it finds food, since the simulator uses it to tell when to stop and call 'bugdead'. */ define fed(); true -> killed; true -> succeeded; enddefine; /* bug_xW(): Bug's x co-ordinate. */ define bug_xW(); world.bw_bug_xW; enddefine; /* bug_yW(): Bug's y co-ordinate. */ define bug_yW(); world.bw_bug_yW; enddefine; /* forwardvector(): Bug's forwardvector: a unit vector pointing in his current forward direction. This is the same as the Y-axis of his local co-ordinate system. */ define forwardvector(); world.bw_forwardvector; enddefine; /* rightvector(): Bug's rightvector: a unit vector pointing in his current right direction. This is the same as the X-axis of his local co-ordinate system, and is his forwardvector rotated right by 90 degrees. */ define rightvector(); world.bw_rightvector; enddefine; /* direction(): The direction Bug is heading in, as one of "east", "north", "west", "south". The obvious correspondance holds between forwardvector and direction: (0,1) -> north, (1,0) -> east. */ define direction(); world.bw_direction; enddefine; /* object_at_bug(): The object in Bug's square, as a character. ` ` if none. */ define object_at_bug(); world( bug_xW(), bug_yW() ); enddefine; /* initial_energy(): The energy Bug gets initially and on each reincarnation. */ define initial_energy(); world.bw_initial_energy; enddefine; /* reply(): The last reply Bug made, i.e. the argument of the last call of say(). Will be [] if Bug has said nothing since the start of the current cycle. */ define reply(); world.bw_reply; enddefine; /* xV(): The X-position of Bug within its visual array. The left-hand bottom corner of this array is (1,1). Note: this position is set when the bug is created, and does not change as it moves. In this version of Eden, it is fixed at 3. */ define xV(); world.bw_xV; enddefine; /* yV(): The Y-position of Bug within its visual array. In this version of Eden, it is fixed at 2. */ define yV(); world.bw_yV; enddefine; /* one_forward( xW, yW ): Returns the co-ordinates of the square one place forward from (xW,yW). */ define one_forward( xW, yW ); lvars xW, yW; bw_rel_forward( world, xW, yW, 1 ); enddefine; /* one_back( xW, yW ): Returns the co-ordinates of the square one place back from (xW,yW). */ define one_back( xW, yW); lvars xW, yW; bw_rel_forward( world, xW, yW, -1 ); enddefine; /* one_left( xW, yW ): Returns the co-ordinates of the square one place left from (xW,yW). */ define one_left( xW, yW ); lvars xW, yW; bw_rel_right( world, xW, yW, -1 ); enddefine; /* one_right( xW, yW ): Returns the co-ordinates of the square one place right from (xW,yW). */ define one_right( xW, yW ); lvars xW, yW; bw_rel_right( world, xW, yW, 1 ); enddefine; /* world_width(): The width (along the world's X-axis) of the current world. */ define world_width(); world.bw_world_width; enddefine; /* world_height(): The height (along the world's Y-axis) of the current world. */ define world_height(); world.bw_world_height; enddefine; /* Saving cases. ------------- */ /* save_case( proc ); Save a list made from proc's result(s), in the case file. */ define save_case( proc ); lvars proc; save_stream( [% proc() %] ); enddefine; /* User interface. --------------- */ define bug_using_ved(); use_ved enddefine; define bug_message( message ); lvars message; if not( message.isstring ) then message >< '' -> message; endif; if use_ved then vedputmessage( message ) else pr( message ); 1.nl; endif; enddefine; vars read_sentence;/*forward*/ vars read_item;/*forward*/ /* ask_continue(): Outputs the Eden ``continue'' prompt, and reads and checks replies. Result is one of [ y ] [ n ] [ r ] [ f ^integer ] [ s ^integer ] [ l ^chars ] [ p ^chars ] */ define ask_continue(); lvars reply, number, c; if use_ved then bug_message( 'Continue? y/n/s(low) /f(ast) /l(isten)/p(op)/r(efresh)' ); else bug_message( 'Continue? y/n/s(low) /f(ast) /l(isten)/p(op)' ); endif; read_item() -> reply; if reply="s" or reply="f" then read_item() -> number; if not( number.isinteger ) then bug_message( 'Number expected after f or s'); signal_error(); ask_continue(); else [% reply, number %] endif; elseif reply="y" or reply="n" then [% reply %] elseif reply="r" and use_ved then [% reply %] elseif reply="l" or reply="p" then [% reply, read_sentence(reply) %] else bug_message( 'Bad reply' ); signal_error(); ask_continue() endif; enddefine; /* read_char(): Reads one character, in Ved and non-Ved modes. In Ved mode, the character is read immediately, and not echoed. In non-Ved mode, it will be echoed, and will not come in until the next RETURN. */ define read_char(); lvars c; if use_ved then rawcharin() -> c; c else charin(); endif; enddefine; vars rawitemin; incharitem( rawcharin )-> rawitemin; /* Used by read_item(). */ /* read_item(): Reads one item, in Ved and non-Ved modes. In Ved mode, the item is read immediately, and not echoed. In non-Ved mode, it will be echoed, and will not come in until the next RETURN. */ define read_item(); if use_ved then rawitemin() else itemread() endif; enddefine; /* read_sentence(what): Reads a sentence for Bug to ``listen'' to. In Ved mode, it allows the sentence to be edited. The sentence is returned as a list of characters. This routine is also used to read a line of Pop for Bug to obey. Whether it's a sentence or code is indicated by the -what- argument: "l" or "p". */ define read_sentence(what); lvars what; lvars c, old_vedstatic; if use_ved then true -> vedstatic; vedstatic -> old_vedstatic; vedjumpto( heard_line, 1 ); if what = "l" then vedreadlinechars( 'Type sentence: finish with RETURN or ENTER', '?' ); else vedreadlinechars( 'Type Pop-11 statements: finish with RETURN or ENTER', ':' ); endif; old_vedstatic -> vedstatic; else if what = "l" then pr( 'Type sentence on next line: finish with RETURN\n' ); else pr( 'Type Pop-11 statements on next line: finish with RETURN\n' ); endif; [% while ( read_char() -> c; if c = `\n` then false else c; true endif ) do; endwhile %] endif; enddefine; /* signal_error(): Signal an error by bell, if possible, in both Ved and non-Ved modes. */ define signal_error(); if use_ved then vedscreenbell() endif; enddefine; /* Viewing thoughts ---------------- A (Ved-)window onto the brain. */ define syntax bug_view(); sysxcomp(); sysCALL( "to_view" ); enddefine; define bug_view_on(); false -> no_view; enddefine; define bug_view_off(); true -> no_view; enddefine; define to_view( Writer ); lvars Writer; lvars c; if no_view then return endif; if use_ved then cucharout -> c; vedcharinsert -> cucharout; if not( view_opened ) then 12 -> vedstartwindow; vedselect( 'view' ); ved_clear(); true -> view_opened; else vedselect( 'view' ) endif; Writer(); c -> cucharout; vedselect( 'bug' ); else Writer(); endif; enddefine; /* Sections -------- These routines deal with access to things in the code of bugs. */ /* from_section( sect, name ): Returns the value of variable name in sect. Used for accessing the brain procedures. We can't use $- which would be easier: it seems the identifier has to be already defined when you write that, which these won't be. */ define from_section( sect, id ); lvars sect, id; lvars wi; word_identifier( id, sect, true ) -> wi; if wi = false then FAULT( 'from_section: identifier not found', [%sect,id%] ) else wi.valof; endif; enddefine; /* call_in_section( sect, proc ): */ define call_in_section( sect, proc ); vars sect, proc; sect -> current_section; proc(); section_subsect( "eden", pop_section, false ) -> current_section; enddefine; endsection;