/* EDEN_CORE.P */ section $-eden => eden, resume_eden, exit_eden, define_bug, say, heard_from, inventory, retina, 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: "no_ved" "batch" "plog" [% "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 "no_ved", "batch" and "plog" 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( "no_ved" ); eden( 'MYBUG', 'tw1', [ save mycases ^retina ], "batch" ); eden( 'manual_bug', 'MyWorld', [% "save", 'CASES', retina %], "no_ved" ); The "plog" 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 %], "no_ved" ); 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 ^retina ] [ save fred ] [ save cases ^myproc ] 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, 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 ^retina ^smell ] then the first list saved might be [% R, "forward", "forward" %] where 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 ^retina ^smell ] then each call of r would give you a list whose first element was the 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 "no_ved" 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( "no_ved" ); eden( 'talk_bug', 'tw3', "no_ved" ); 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. Result from bugdead: rerun. Sections -------- On exit, 'eden' resets the current section to 'pop_section': see the implementation notes. Resuming Eden: ============== PUBLIC resume_eden(): This resumes Eden from where it was last exited, using the current world and bug. Exiting Eden: ============= PUBLIC exit_eden(): Exits cleanly from Eden. You should not use this during the competition. 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 heard_from(id): The last sentence ``heard'' by Bug from id. [] if none, otherwise a list of characters. PUBLIC inventory(): The contents of Bug's inventory. A character representing an object: ` ` if none. Special output: =============== PUBLIC bug_using_ved(): True if Ved is being used, i.e. if 'eden' was called without the "no_ved" 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. EDEN_CORE.PL also assumes this. 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 ... 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 ----------- The file-handling code in LOAD_FILES.P and ADD_FILE_DEFAULTS.P may need to be changed. 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; needs edit_world; needs edit_view; vars tracing_eden; false -> tracing_eden; /* True if internals of simulation are to be traced. */ vars world; /* The current world. */ vars last_action; /* The bug's last action. */ vars time; /* The time in the current life. */ vars bug_ids; /* The numbers of the current bugs. */ 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 "plog". */ 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. */ /* 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 bugs, 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 or ().dup.islist then () -> world_file; () -> bugs; endif else "same_world" -> world_file; "same_bugs" -> bugs; endif; /* Takes the filenames, if present. */ eden_( bugs, world_file, false ); pop_section -> current_section; enddefine; define resume_eden(); eden_( "same_bugs", "same_world", true ); pop_section -> current_section; enddefine; define global exit_eden(); if tracing_eden then pr('Calling exit_eden()\n') endif; exitfrom(simulate); 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 = "no_ved" then false -> use_ved; set_default_parameters(); case = "ved" then true -> 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 = "plog" then "plog" -> language; set_default_parameters(); else top; endswitchon endif; enddefine; vars simulate, load_world, call_in_section;/*forward*/ /* eden_( bug_file, world_file, restart ): This is called by 'eden' once it has taken off the options. world_file will be either "same_world" or a filename. restart will be either true if starting Eden from scratch, or false if resuming an old run. The global variables denoting options will be set as follows: use_ved: true or false. mode: "slow" or "batch". language: "pop" or "plog". 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_spec, world_file, restart ); lvars bug_spec, world_file, restart; /* Load world. */ unless world_file = "same_world" or restart then load_world( world_file ); endunless; /* Load bugs. */ unless bug_spec = "same_bugs" or bug_spec = "same_bug" then bw_set_retina_size( world, 5, 7, 3, 2 ); printf('Loading bugs\n',[]); load_bugs( bug_spec ); printf('Loaded bugs\n',[]); 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; /* Loading bugs ------------ */ vars _bug_procs; /* Will be set by a call of define_bug. */ define load_bugs( bug_spec ); lvars bug_spec; lvars is_file, i, bug; /* 1) If it's a single file-name, convert to a word. */ false -> is_file; if language="pop" and bug_spec.isstring then consword(bug_spec) -> bug_spec; true -> is_file; elseif language="plog" and bug_spec.is_character_list then character_list_to_word(bug_spec) -> bug_spec; true -> is_file endif; /* 2) If it's a single file-name, load that file, otherwise treat as a procedure or list of procedures. */ if is_file then [] -> _bug_procs; load_bug_code( bug_spec ); /* May affect _bug_procs via define_bug. */ if _bug_procs = [] then if language="pop" then [% call_in_section(pop_section,valof(%bug_spec%)) %] elseif language="plog" then [% bug_spec %] endif -> _bug_procs endif; else if not(islist(bug_spec)) then [%bug_spec%] else bug_spec endif -> _bug_procs; endif; [% for i to length(_bug_procs) do if not(_bug_procs(i).isundef) then bw_new_bug( world, i, make_procedure(_bug_procs(i)) ); bw_initialise_bug( world, "east" ); i endif; endfor; %] -> bug_ids; enddefine; define is_character_list(l); lvars l; lvars c; if not(islist(l)) then return(false) else for c in l do if not(isinteger(c)) then return(false) endif; endfor; endif; true enddefine; define character_list_to_word(l); lvars l; consword( explode(l), length(l) ); enddefine; define make_procedure(proc); lvars proc; if language="pop" then proc elseif language="plog" then call_in_section(%pop_section,prolog_invoke(%proc%)%) endif; enddefine; /* load_bug_code( bug_file ): bug_file is a word or string. Compile it. */ define load_bug_code( bug_file ); lvars bug_file; /* 1) Get the full filename. */ if language = "pop" then load_file( bug_file, '.p', systranslate('EDEN_PATH') ) -> bug_file; elseif language = "plog" then load_file( bug_file, '.pl', systranslate('EDEN_PATH') ) -> bug_file; endif; /* 2) Error if file not found. */ if bug_file = false then mishap( 'load_bug_code: 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 = "plog" then call_in_section( pop_section, prolog_compile(%bug_file%) ); endif; enddefine; define global define_bug(id,proc); lvars id, proc; if id > length(_bug_procs) then expand( _bug_procs, id ) -> _bug_procs; endif; proc -> _bug_procs(id); enddefine; /* Eden. ----- */ vars save_case, reset_world, replace_world, life, retina, sentence, inventory;/*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; /* 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 = "plog" then [% retina, sentence, inventory %] -> 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() -> last_action; save_case( proc<>identfn(%last_action%) ); last_action endprocedure(% brain %) -> brain; endif; /* Reset world. */ reset_world(); /* Run lives repeatedly. 'disposition' is the result returned by bugdead. */ 0 -> no_of_lives; until ( 1 + no_of_lives -> no_of_lives; life( no_of_lives ) -> disposition; if disposition = "stop" then true elseif disposition = "rerun" then reset_world(); true elseif disposition matches [ rerun ? ^ !worldfile ] then replace_world( worldfile><'' ); reset_world(); true else FAULT( 'simulate: illegal disposition', [%disposition%] ); endif ) do enduntil; if tracing_eden then pr('About to return from simulate\n') endif; enddefine; /* Individual lives and cycles. ---------------------------- */ vars initialise_display, say, 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. */ vars bugs_all_dead; /* Set false at the start of a life. */ /* 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 cycles_left; 0 -> time; /* Ensure that time is defined before the first display, in case the user wants to display it. */ 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; false -> bugs_all_dead; until bugs_all_dead do /* Set heard and said sentences to null on each cycle. */ bw_clear_sentences( world ); /* 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; if tracing_eden then pr('About to call cycle\n') endif; cycle(); if tracing_eden then pr('Returned from cycle\n') endif; cycles_left - 1 -> cycles_left; if mode = "slow" then show_info(); show_world(); elseif mode = "fast" then bug_message( 'Fast mode: time = '>