Today, I was trying to organize my old Hugo test code, and I came across a problem I never did find a solution for. In my effort to acquaint myself with every facet of Hugo, I was playing around with identical classes.
In my test case, I had a dirty dishes identical class. The theory was, when the player washed them, they'd be removed from that class and added to the clean dishes identical class. In practice, my attempts either break the parsing so that after washing X number of dishes, it thinks I am trying to refer to the identical classes themselves, or incorrectly groups identicals.
Anyhow, I played with it some more today with no luck so far. In any case, I thought I'd throw it out here in case anybody wanted the challenge.
Switching Identical Classes
Moderators: Ice Cream Jonsey, joltcountry
-
- Posts: 2255
- Joined: Mon Apr 29, 2002 6:23 pm
- Location: Milwaukee
-
- Posts: 2255
- Joined: Mon Apr 29, 2002 6:23 pm
- Location: Milwaukee
I did recently figure this out, and to save anybody else from working on this, I'll share what I found here.
First off, it wasn't until way late in the game that I realized that AddIdentical, the routine for adding objects to identical classes, had a typo. It wasn't until I properly added objects uses Future Boy!'s AddPropValue routine and I saw it worked that it occurred to me to take a closer look at AddIdentical.
That fixed the most obvious bug (at the end of dishwashing, the PC was left with "two clean dishes three clean dishes"), but without help, referring to the three clean dishes was still redirected to the now-nonexistant dirty dishes.
Originally, I modified ParsePluralObjects so it'd check for another plural class object with a higher parse_rank so it wouldn't always go with the first identical class that shares a noun or single_noun that it is looking for.
Eventually, I decided that, in this case, I wasn't going to be using the dirtydishes class again so might as well take it out of play. ParsePluralObjects puts all identical class objects into the identical_class object itself, so I just removed it and reset its type property so it wouldn't be put back there again.
Here is some of the code:
First off, it wasn't until way late in the game that I realized that AddIdentical, the routine for adding objects to identical classes, had a typo. It wasn't until I properly added objects uses Future Boy!'s AddPropValue routine and I saw it worked that it occurred to me to take a closer look at AddIdentical.
That fixed the most obvious bug (at the end of dishwashing, the PC was left with "two clean dishes three clean dishes"), but without help, referring to the three clean dishes was still redirected to the now-nonexistant dirty dishes.
Originally, I modified ParsePluralObjects so it'd check for another plural class object with a higher parse_rank so it wouldn't always go with the first identical class that shares a noun or single_noun that it is looking for.
Eventually, I decided that, in this case, I wasn't going to be using the dirtydishes class again so might as well take it out of play. ParsePluralObjects puts all identical class objects into the identical_class object itself, so I just removed it and reset its type property so it wouldn't be put back there again.
Here is some of the code:
Code: Select all
identical_class dirtydishes "dirty dishes"
{
article "some"
plural_of dish1 dish2 dish3
noun "dishes"
single_noun "dish"
adjective "dirty"
}
identical_class cleandishes "clean dishes"
{
plural_of #3
noun "dishes"
single_noun "dish"
adjective "clean"
is known
}
routine DoWash
{
if object.identical_to ~= cleandishes, dirtydishes
"That doesn't need washing."
elseif object.identical_to = cleandishes
"That dish is already clean."
else
{
RemoveIdentical(object)
object.identical_to = cleandishes
AddIdentical(object,cleandishes)
object.name = "clean dish"
object.adjective = "clean"
if not PropertyCount(dirtydishes, plural_of)
{
"You wash the last dirty dish."
remove dirtydishes ! move it out of the identical_class object
dirtydishes.type = 0
}
else
"You wash one of the dirty dishes."
}
return true
}
routine PropertyCount(obj, prop)
{
local a, total
for (a=1; a<=obj.#prop; a++)
{
if obj.prop #a: total++
}
return total
}
-
- Posts: 89
- Joined: Wed Jan 04, 2012 2:57 pm
- Location: Texas
I finally had a chance to look at this today. I put your above code into a game with three dirty dish items, but I couldn't get it to work properly. Then I looked at AddIdentical and found the typo you were referring to:
Here is the corrected routine:
Now it works perfectly. It took me a little while to find the typo because I was stuck on "return j". I thought maybe this should return "true" instead. Then I realized returning any number above 0 is the equivalent to returning true(not just 1).
Its not surprising that no one has noticed this typo before, since you may be the first person to ever try it. I see that even objlib.h doesn't use AddIdentical. I think it's great that you're playing with plural and identical object classes. I've only had the opportunity to use them a couple of times, but I've always been impressed by how elegantly they handle manipulating multiple items.
Code: Select all
for (j=1; j<i.#plural_of; j++)
Here is the corrected routine:
Code: Select all
replace AddIdentical(obj, i)
{
local j
for (j=1; j<=i.#plural_of; j++)
{
if i.plural_of #j = 0 ! a blank slot
{
i.plural_of #j = obj ! add it
return j
}
}
return false
}
Its not surprising that no one has noticed this typo before, since you may be the first person to ever try it. I see that even objlib.h doesn't use AddIdentical. I think it's great that you're playing with plural and identical object classes. I've only had the opportunity to use them a couple of times, but I've always been impressed by how elegantly they handle manipulating multiple items.
-
- Posts: 2255
- Joined: Mon Apr 29, 2002 6:23 pm
- Location: Milwaukee
Heh, I didn't mean to make anyone go track it down themselves, but yeah, I guess I forgot to point it out here (I'm pretty sure I considered it but, I dunno, I guess I wanted to keep my code above simple or something).
I think AddIdentical is mentioned in the Hugo Book, but in any case, yeah, one of the big motivations for me to help instigate Hugo by Example is that I particularly wanted a better understanding of utility routines like that (that aren't necessarily covered all that well in the Hugo Book).
And yeah, I can't even imagine the amount of thought that goes into designing working parser routines, and Hugo's handling works remarkably well.
I think AddIdentical is mentioned in the Hugo Book, but in any case, yeah, one of the big motivations for me to help instigate Hugo by Example is that I particularly wanted a better understanding of utility routines like that (that aren't necessarily covered all that well in the Hugo Book).
And yeah, I can't even imagine the amount of thought that goes into designing working parser routines, and Hugo's handling works remarkably well.
-
- Posts: 2255
- Joined: Mon Apr 29, 2002 6:23 pm
- Location: Milwaukee
Re: Switching Identical Classes
Well, like I said in another thread, I've been digging through old problems just to see if I can shed new light on them. This identical class problem has been on my mind for years. Recently, I fixed a problem in the engine that occasionally provided a wrong object to ParseError, and I thought there was a small chance that issue had affected this problem, causing the point where the command seems to get redirected for an explicable reason.
As a refresher, the problem was this. I had coded a situation in which there were multiple identical dirty dishes. As the player washes each dish, it gets removed from the dirty dishes and added to the identical dishes group. At some point, >WASH DISH would result in "You can't see that.", instead of washing the final dirty dish.
Anyhow, the recent heparse,c fix did not have any effect on this, so I looked at the whole problem again. I noticed that if I washed all of the dirty dishes consecutively, they were all washed properly. If I >LOOKed in between steps, the problem came up.
I imagine that I first encountered this problem before I was comfortable with the debugger and before I had even learned the usefulness of the Hugofix commands. Running the game with Hugofix's Parser Monitor on showed that the code was successfully getting all the way to the Perform stage and it was choking from there. Using the debugger, I saw that the important code was in the before property for the plural class. It was checking to make sure the object had the workflag attribute, and it did not.
This explained why >LOOKing was changing the behavior, as DescribeRoom applies and clears the "already_listed" attribute to objects as it goes through the room, and already_listed is an alias of workflag.
I then searched through the library source to see where workflag was set in the processing of plurals. I eventually found the culprit in this code:
The problem was that in code like mine, where a plural class object has values removed throughout the course of the game (as dirty dishes become clean), some of the elements in dirtydishes.plural_of are going to be 0. So this code then proceeds to execute FindObject(0,location) which returns true since 0 (nothing) is the parent of the location. So then the code above says, ok, we found an object that works and proceeds, setting 0 as workflag and breaking out of the loop before any of the actual plural objects can be workflagged.
So, putting most of that code in an "if k" conditional fixed the problem.
The further a Hugo author gets into Hugo, the debugger (and Hugofix) just gets more and more crucial. Anyhow, the plural class system does so much additional parsing on top of a normal command that there may be more bugs to discover, but we'll take those as we find them.
As a refresher, the problem was this. I had coded a situation in which there were multiple identical dirty dishes. As the player washes each dish, it gets removed from the dirty dishes and added to the identical dishes group. At some point, >WASH DISH would result in "You can't see that.", instead of washing the final dirty dish.
Anyhow, the recent heparse,c fix did not have any effect on this, so I looked at the whole problem again. I noticed that if I washed all of the dirty dishes consecutively, they were all washed properly. If I >LOOKed in between steps, the problem came up.
I imagine that I first encountered this problem before I was comfortable with the debugger and before I had even learned the usefulness of the Hugofix commands. Running the game with Hugofix's Parser Monitor on showed that the code was successfully getting all the way to the Perform stage and it was choking from there. Using the debugger, I saw that the important code was in the before property for the plural class. It was checking to make sure the object had the workflag attribute, and it did not.
This explained why >LOOKing was changing the behavior, as DescribeRoom applies and clears the "already_listed" attribute to objects as it goes through the room, and already_listed is an alias of workflag.
I then searched through the library source to see where workflag was set in the processing of plurals. I eventually found the culprit in this code:
Code: Select all
for (i=1; i<=pobj.#plural_of; i++)
{
k = pobj.plural_of #i
! explicitly notheld object
if pluralobj_heldmode = -1 and k is not workflag
{
if FindObject(k, loc) and k not in actor
{
n = k
k is workflag
j++
}
}
elseif pluralobj_heldmode ~= -1
k is not workflag
! explicitly held object
if pluralobj_heldmode = 1
{
if k in actor
{
n = k
k is workflag
j++
}
}
! or neither
elseif pluralobj_heldmode = 0
{
if FindObject(k, loc)
{
n = k
k is workflag
j++
}
}
if n and plural_type = single_noun: break
if j and j = pobj_number: break
}
So, putting most of that code in an "if k" conditional fixed the problem.
The further a Hugo author gets into Hugo, the debugger (and Hugofix) just gets more and more crucial. Anyhow, the plural class system does so much additional parsing on top of a normal command that there may be more bugs to discover, but we'll take those as we find them.
-
- Posts: 2255
- Joined: Mon Apr 29, 2002 6:23 pm
- Location: Milwaukee
Re: Switching Identical Classes
While digging through old folders of old problems, I found another plural class conundrum I ran into years ago. I was testing how well Hugo's plural class system worked when an identical class subset fits into a larger plural class. In my example game, I had a quarter, a dime, three gold coins, three silver coins, and three platinum coins. Ideally, >GET COINS should get them all but referring to gold coins or such should only apply to the relevant identical class.
Anyhow, this was not the case then and it was not the case more recently.
After playing around with different facets of the issue, I eventually honed in on the part of ParsePluralObjects which picks what the relevant class object is. ParsePluralObjects does a fair amount of parsing without much help from the engine. Basically, all one can do at that point in the parsing cycle is go through the command word by word and test words for being nouns or adjectives.
And the existing code only checked for nouns in the happy belief that plural classes would never share nouns or something. Anyhow, I rewrote that section so that once it finds a matching noun, it also tests some words to make sure there isn't also an adjective more applicable to another class.
My coins work now, and this will end up in Roodylib eventually. The one extra thing that my code needed for optimal behavior was some parse_rank tweaks which would automatically lower the ranking of classes with adjectives ("gold coins", etc) if an adjective is absent because we want >GET COINS to always refer to all of the coins in general and not just the last group of coins that were interacted with.
parse_rank is a fickle creature, and I don't have faith that the rules for my coins would apply to all uses for plural classes, so that will not be added to Roodylib. I'll leave it up to authors to finesse it for their games.
Anyhow, this was not the case then and it was not the case more recently.
After playing around with different facets of the issue, I eventually honed in on the part of ParsePluralObjects which picks what the relevant class object is. ParsePluralObjects does a fair amount of parsing without much help from the engine. Basically, all one can do at that point in the parsing cycle is go through the command word by word and test words for being nouns or adjectives.
And the existing code only checked for nouns in the happy belief that plural classes would never share nouns or something. Anyhow, I rewrote that section so that once it finds a matching noun, it also tests some words to make sure there isn't also an adjective more applicable to another class.
My coins work now, and this will end up in Roodylib eventually. The one extra thing that my code needed for optimal behavior was some parse_rank tweaks which would automatically lower the ranking of classes with adjectives ("gold coins", etc) if an adjective is absent because we want >GET COINS to always refer to all of the coins in general and not just the last group of coins that were interacted with.
parse_rank is a fickle creature, and I don't have faith that the rules for my coins would apply to all uses for plural classes, so that will not be added to Roodylib. I'll leave it up to authors to finesse it for their games.