Magsbot Class, Session 7
Saturday April 26, 2003 2pm VRT
note: added remarks or paraphrases are in [brackets] and/or italicized, and the order of some chat has been changed for clarity.

ZebLith: Ah, good morning Magine. :)
Magine: so, did you look at ...uh, whatever it was i told you to look at? :D
ZebLith: I think it was [SayLong] but I'm not sure.  I did right after class.  lemme check ^^
Magine: 'k
ZebLith: Ah, it was [PickAv]...
Magine: ok, so what did you  think?
Magine: did it make sense?
ZebLith: Hehe, somewhat.  I haven't looked up all the variables yet.
Magine: well, we can go thru it today. but any other questions from last time?
ZebLith: no questions :)
Magine: ok, then let's go thru the [PickAv] button to start with
Magine: the first command in the button is RESETNEARBY
Magine: (you probably want to look at the code in the button to follow along, if you aren't already doing that)
Magine: the RESETNEARBY command works with the $listnext function
Magine: the $listnext function retrieves the "next" name in the list of nearby avs
Magine: each time you use $listnext, it moves the "next" pointer forward one name
Magine: and RESETNEARBY moves the pointer back to the beginning of the list
Magine: so by calling RESETNEARBY and then calling $listnext multiple times, you can get all the names from the nearby list
Magine: you know when you get to the end of the list only by using the count of the names in the list,
Magine: and that count is gotten from the @avspresent function
Magine: which returns the number of nearby avs
Magine: so, you can see that the pickav button first checks to see if there are any avs nearby,
Magine: and if so, it uses a loop to copy all the names from the nearby list into a string list named "nearby"
Magine: that's what the ENLIST command is doing there
Magine: actually, you can tell how long ago i wrote the PickAv button, but i would do that kind of differently now.
Magine: early versions of magsbot had no FOR loop, only a WHILE loop
Magine: i'm assuming you understand how the loops work, right?
ZebLith: yes :)
Magine: but for the benefit of any class log readers, i'll explain briefly (tho it's probably self-evident)
Magine: that the commands within a FOR or WHILE loop are repeated
Magine: the WHILE loop repeats as long as the initial expression is true (not 0)
Magine: and the FOR loop, which looks like this:
Magine: for i 1 10 { rem do something }
Magine: the FOR loop assigns numbers to the specified variable (in this case i)
Magine: the first time through the loop, i will be assigned the first specified number ( 1 in this case )
Magine: and the loop will be repeated for each consecutive number, up to the second specified number (10 in this case)
Magine: you could also specify an increment other than 1,
Magine: like this:
Magine: for i 1 10 2 { rem do something }
Magine: in which case @i would be incremented by 2 each time instead of 1 (the default)
Magine: and finally, you can also use a negative increment, like
Magine: for i 10 1 -1 { rem do something }
Magine: to count down instead of up.
Magine: so, getting back to the PickAv button,
Magine: instead of @i=0; while @i<@avspresent { ENLIST nearby $listnext; @i=@i+1 }
Magine: how would i do the same thing with a for loop? (this is a pop quiz, heheh)
ZebLith: eerg :D  alright.  let's see...
Magine: this should be very simple :)
ZebLith: "should" ;)  Well I'm not sure what to do for the maximum value for i...
Magine: ok, you would do
Magine: for i 0 @avspresent-1 { enlist nearby $listnext }
Magine: or, since the value of @i is not important in the loop,
Magine: for i 1 @avspresent { enlist nearby $listnext }
Magine: would do that same thing
Magine: actually there is one other improvement you could make there
Magine: since @avspresent is a function, and it might be faster if you didn't call the function each time thru the loop,
Magine: you could get the number and put it in a variable first, like this:
Magine: @ct=@avspresent; for i 1 @ct { rem etc. }
Magine: make sense?
Magine: ok, to continue...
ZebLith: yes, I wasn't sure if you could do functions while setting the range.  okay :)
Magine: you can, it's just usually faster if you use a variable instead
Magine: anyway, once you have the list of nearby av names in the list
Magine: you can use the $pick function with that list
Magine: to let the user select one of the names.
Magine: the $pick function displays a popup window with a list of all global variables that have the specified prefix
Magine: in other words, all global variables whose name begins as specified.
Magine: in this case i use $listprefix[nearby]
Magine: but i could just as easily used
Magine: "$nearby:"
Magine: because all the $listprefix function does is to add the $ at the beginning and : at the end of a string
Magine: so $listprefix takes "nearby" and changes it to "$nearby:"
Magine: therefore, the $pick function here shows a popup list of all global variables whose names begin with "$nearby:"
Magine: and as i explained in a previous class, lists in magsbot as just a group of global variables with a similar name
Magine: so when ENLIST was used to put an avname into the list named "nearby"
Magine: all it did was to create global variables named
Magine: $nearby:1, $nearby:2, $nearby:3 and so forth
Magine: and of course those variables will each contain a different av name
Magine: the whole point of the loop to put the names of nearby avs into a list,
Magine: was just so the $pick function could be used to present a list of those names to the user,
Magine: and get the user's selection from that list.
Magine: when the user clicks a name in the list, the $pick function will return that name
Magine: therefore, the variable named $target that you see there, will be assigned the name from the list that the user chooses.
Magine: make sense?
ZebLith: perfect sense :)
Magine: ok :)
Magine: after getting the user's pick, you don't want that list hanging around and wasting memory,
Magine: so you use the FREELIST command to get rid of it.
Magine: next you check to make sure that the user really did choose a name from the list,
Magine: because if the user just clicked "cancel" then $pick would return a empty string and $target would just be "" (empty)
Magine: so you use the @neq function (which compares two strings and returns true if they are not equal)
Magine: and if $target is not empty, then the REPORT command prints the name in the chat window
Magine: now, the next section that says POPUP "There are no avatars" etc.
Magine: is the "else" clause of the original IF @avspresent at the beginning of the code
Magine: if that "there are avs" popup message will only appear if there are no avs present
Magine: and $target is assigned an empty string just so the next command, RETURN, will have something to return even if there are no avs present
Magine: so finally, RETURN $target passes the user's choice back to the calling routine
Magine: so when you use $ftn["[PickAv]"]
Magine: it will return that choice
Magine: everything clear?
ZebLith: yes :)
Magine: ok cool :)
Magine: well, there are scores if not hundreds of buttons, and it would take forever to go thru them all,
Magine: so i will let you study them on your own.
Magine: but i'll try to explain a few things that are commonly done in magsbot code.
Magine: specifically, i should tell you more about lists,
Magine: since they're used all over the place in all my magsbot programs.
ZebLith: that'd be great, I was just about to ask if you would :)
Magine: if you want to open a couple of web browser windows, take a look at http://www.turtleflight.com/mbh/mh_actions.htm#assign
Magine: and also at http://www.turtleflight.com/mbh/mh_lists.htm
Magine: on those pages you can see many commands and functions for dealing with lists
Magine: and you can read those later on, but right now i'll explain some basic list operations.
Magine: first, creating a list :)
Magine: you can read a list in from a text file using GETLIST and other commands,
Magine: as explained on those pages,
Magine: but more commonly you'll add to lists one item at a time
Magine: using ENLIST, ENLIST_, SETITEM or SETITEM_
Magine: the difference between the two variations of those commands (e.g. ENLIST and ENLIST_) is that the commands that end in an underscore _
Magine: are for numeric lists,
Magine: and the non-underscore versions are for string lists.
Magine: the difference between ENLIST and SETITEM is that ENLIST will add an item to a list and automatically give it an item number
Magine: whereas with SETITEM you specify the number yourself
Magine: you should note that the item numbers in magsbot lists are NOT indexes in the list
Magine: the item numbers are arbitrary
Magine: for instance, if you have a list with 3 items in it
Magine: the list may consist of global variables like $mylist:1  $mylist:2  $mylist:3
Magine: but it doesn't have to use consecutive numbers like that
Magine: it can just as easily be $mylist:12  $mylist:2478  $mylist:92
Magine: and even if the numbers are consecutive as in the first example,
Magine: if you delete the middle one ($mylist:2) the numbers of the other items will not change
Magine: so you would have a list with two items, but still named $mylist:1 and $mylist:3
Magine: in some ways this is a bit awkward perhaps,
Magine: but it does have some advantages
Magine: in that you can use object numbers, avatar session numbers, citizen numbers or any other numbers as item numbers
Magine: allowing you to look up items in a list easily
Magine: so for instance,
Magine: if you wanted a list that would allow you to find someone's cit number quickly,
Magine: you could do
Magine: SETITEM mylist @atr[citizen_number] $atr[avatar_name]
Magine: in the AVATARADD event
Magine: which would create an item in $mylist like
Magine: $mylist:28777
Magine: with the value (for instance) of "Magine"
Magine: so later on in your program when you need to know the citizen number (e.g. in some event where it is not normally available)
Magine: you could get it from the list using
Magine: @itemnum[mylist,Magine]
Magine: or the other way around, to find the name from the cit number:
Magine: $litem[mylist,28777]
Magine: do you see how that works? :)
ZebLith: yes :)
Magine: to remove an item from a list, you can use FREEITEM or FREEITEM_ (for string or numeric lists, respectively)
Magine: or you can use FREELIST or FREELIST_ to get rid of the entire list.
Magine: now one important thing to note about freeing list items or lists
Magine: is that it can be a tiny bit slow (comparatively)
Magine: due to some changes i made in magsbot 4.0 to speed up variable access.
Magine: you can read about that in the tech note under the DEFER command on http://www.turtleflight.com/mbh/mh_actions.htm#assign
Magine: but basically, it amounts to this: when you free a global variable or a list, magsbot has to make a quick check
Magine: of the entire behavior table and all the action buttons
Magine: in order to update any references to the freed variable.
Magine: so, when you write your program, you want to try to avoid calling FREEITEM or FREELIST too often.
Magine: and also, if you are freeing several variables or several lists at once,
Magine: you should use the DEFER command with those FREEITEM, etc commands within the DEFER clause,
Magine: in order to delay the update of the behavior table and buttons until all the variables are freed,
Magine: so you don't do the update over and over for each variable.
Magine: does that make some sense? :)
ZebLith: yes, very clear. :)
Magine: ok good :)
Magine: to look at individual items in a list,
Magine: you use $listitem[$list,@i]  (string lists) or @listitem[$list,@i] (numeric lists) where $list is the listname and @i is the item number
Magine: or you can use $litem and @litem instead of $listitem and @listitem,
Magine: the only difference being that $litem and @litem won't cause an error if the specified item is missing
Magine: $litem will just return an empty string and @litem will return 0, if the specified item is missing.
Magine: now, if you want to examine all the items in a list one at a time
Magine: you can use $nextitem[$list] or @nextitem[$list]
Magine: after calling RESETLIST $list to set the pointer back to the top of the list.
Magine: or, instead of $nextitem/@nextitem, you can use @nextnum (string lists) or @nextnum_ (numeric lists) to get the item numbers instead of the values,
Magine: then you could use $litem or @litem to get the values (having gotten the item number)
Magine: i'm trying to think of an example, somewhere i use this technique that isn't too complicated otherwise
Magine: well i'll just use an imaginary example :D
ZebLith: hehe ^^
Magine: suppose you have a string list named $mylist
Magine: and you just want to REPORT each item the the chat window
Magine: you would do this:

RESETLIST $mylist;
@ct=@itemcount[$mylist];
FOR i 1 @ct {
  REPORT $nextitem[$mylist] }
Magine: simple enough, huh? :)
Magine: i hope :D
ZebLith: yes ^^
Magine: ok, next example...
Magine: suppose you want to go thru a string list and delete certain items, for instance only items that begin with "a"
Magine: you might start out like this:
RESETLIST $mylist;
@ct=@itemcount[$mylist];
FOR i 1 @ct {
  @n=@nextnum[$mylist];
  FREEITEM $mylist @n };
Magine: instead of getting the next value, you get the next item number
Magine: you could also add a report, like this:
RESETLIST $mylist;
@ct=@itemcount[$mylist];
FOR i 1 @ct {
  @n=@nextnum[$mylist];
  REPORT "deleting "+$litem[$mylist,@n];
  FREEITEM $mylist @n };
Magine: you see how that works? :)
ZebLith: yeah :D
Magine: ok good :)
Magine: however there is a fatal flaw in the last two examples i gave above :)
Magine: can you take a wild guess what it might be?
ZebLith: that is rebuilds the list after every delete (no defer command)?
Magine: well that too :)
Magine: but in fact the above examples won't merely be slow, they won't work at all
Magine: because you are deleting items from the list even as you are attempting to go thru it one item at a time
Magine: deleting an item will mess up the pointer to the next item
Magine: and also the count will change
Magine: each item you delete will reduce the count by one, so the @ct you got a the start will become wrong
Magine: so, what you need to do here is,
Magine: you want to go thru the list and figure out what items you want to delete
Magine: (i forgot to add that to the examples, actually...we didn't want to delete all items, only some)
ZebLith: that's what I was thinking, about deleting some items.  Also...
Magine: i should have put :
RESETLIST $mylist;
@ct=@itemcount[$mylist];
FOR i 1 @ct {
  @n=@nextnum[$mylist];
  $x=$litem[$mylist,@n];
  if @eq[$left[$x,1],"a"] {
    FREEITEM $mylist @n }};
Magine: but as i said, that won't work
Magine: because you're deleting items as you go along
Magine: so what you really need to do,
Magine: is to go thru the list and find the items you want to delete,
Magine: but not actually delete them until after the loop is done.
ZebLith: I need to cut this class short.  None of my family members seem to remember I have this class on Saturdays and make plans without my consent, therefore I have an appointment at 10:50 downtown.
Magine: oops ok
Magine: well next time we will discuss the ENQUEUE command and how to use it to solve the problem above :)
ZebLith: Okay, I'll be here.  Sorry about this.  -_-  See you next Saturday, and thanks. :)
Magine: so class dismissed :)
Magine: see ya later :)