microsoft / TextWorld

​TextWorld is a sandbox learning environment for the training and evaluation of reinforcement learning (RL) agents on text-based games.
Other
1.23k stars 189 forks source link

game created with GameMaker doesn't understand 'fridge' #236

Closed gari-marcos closed 4 years ago

gari-marcos commented 4 years ago

Hello, I was trying to create a game with the GameMaker. However, when I create a container named either 'fridge' or 'refrigerator' and I record the quest for the game I get the following message: You can't see any such thing. But it actually appears on the quest:

-= Kitchen =-
You arrive in a kitchen. A typical kind of place. I guess you better just go and
list everything you see here.

You can make out a refrigerator. You bend down to tie your shoe. When you stand
up, you notice a stove. The stove is usual. On the stove you make out a keycard.

If I leave like that when I run the game to play it that element is replaced with other name such as 'Microsoft box' or 'formless chest':

-= Kitchen =-
You've entered a kitchen.

You can make out a safe. Hmmm... what else, what else? You make out a closed
formless chest. You can make out a stove. The stove is usual. On the stove you
can make out a cuboid passkey and an American limited edition keycard.

Do you know what could be causing this problem?

Thank you in advance,

MarcCote commented 4 years ago

Hi. Can you share your script? Also which TextWorld version are you using?

One guess would be that the name for the container (e.g., "fridge") was not provided as an argument. For instance, M.new("c", name="fridge") would fix the name and the text generation system (which is called when recording a quest, and compiling/saving the game) shouldn't change it.

gari-marcos commented 4 years ago

Yeah sure! I am using 1.3.1 version.

import textworld
from textworld import GameMaker
from textworld import g_rng
g_rng.set_seed(20180329)

M = GameMaker()
kitchen = M.new_room("kitchen")
living_room = M.new_room("living room")
bedroom = M.new_room("bedroom")
bathroom = M.new_room("bathroom")
backyard = M.new_room("backyard")

kitchen_living = M.connect(kitchen.east, living_room.west)
living_bedroom = M.connect(living_room.north, bedroom.south)
living_bathroom = M.connect(living_room.east, bathroom.west)
living_backyard = M.connect(living_room.south, backyard.north)

M.set_player(living_room)

kitchen_living_door = M.new_door(kitchen_living, name = "wooden door")
M.add_fact("locked", kitchen_living_door)
living_bedroom_door = M.new_door(living_bedroom, name = "screen door")
M.add_fact("closed", living_bedroom_door)
living_bathroom_door = M.new_door(living_bathroom, name = "metal door")
M.add_fact("open", living_bathroom_door)
living_backyard_door = M.new_door(living_backyard, name = "door")
M.add_fact("open", living_backyard_door)

key = M.new(type = 'k', name = "key")
M.add_fact("match", key, kitchen_living_door)
couch = M.new(type = 's', name = 'couch')
table = M.new(type = 's', name = 'table')
living_room.add(table)
living_room.add(couch)
couch.add(key)

stove = M.new(type = 's', name = 'stove')
chicken = M.new(type = 'f', name = 'chicken')
refrigerator = M.new(type = 'c', name = 'refrigerator')
M.add_fact('closed', refrigerator)
kitchen.add(refrigerator)
kitchen.add(stove)
refrigerator.add(chicken)

bed = M.new(type = 's', name = 'bed')
tv = M.new(type = 's', name ='tv')
bedroom.add(bed)
bedroom.add(tv)

bath = M.new(type = 'c', name = 'bath')
M.add_fact('open', bath)
toilet = M.new(type = 'c', name = 'toilet')
M.add_fact('open', toilet)
sink = M.new(type = 's', name = 'sink')
toothbrush = M.new(type = 'o', name = 'toothbrush')
sink.add(toothbrush)
bathroom.add(bath)
bathroom.add(toilet)
bathroom.add(sink)

bbq = M.new(type = 's', name = 'bbq')
backyard.add(bbq)

M.render(interactive=True)

M.generate_distractors(10)

#quest = M.record_quest()

commands = ['take key from couch', ' unlock wooden door with key', 
            'open wooden door', 'go west', 'open refrigerator', 
            'take chicken from refrigerator', 'put chicken on stove']

quest = M.set_quest_from_commands(commands)

print( " > ".join(quest.commands))
print("\n" + quest.desc)

M.validate()
M.test()

game = M.build()
game_name = "./test_game_1"
game_file = M.compile(game_name)

Last time I tried with 'refrigerator' but I got the same result. When loading the commands from a list, they got processed like this:

take key from couch > unlock wooden door with key > open wooden door > go west > go west > go west > go west

Thank you!

MarcCote commented 4 years ago

I see what's wrong. One of the added distractors is a key for the refrigerator which causes a change in the name. By default, the names for the key and its matching container are picked from a predefined list specified in the grammar (.twg files). I've fixed this in #237.

I'll make a 1.3.2 release, once it is merged. If you wish, you can directly install the branch containing the fix. pip install https://github.com/MarcCote/TextWorld/archive/fix_236.zip

gari-marcos commented 4 years ago

Okay, thank you very much, I finally got it. Just a thing, is there a way to modify the quest description once it's done? I mean, in the game designed above 6 actions are necessary to complete it and in the goal description, they will all appear. Can I modify the goal to mention only the last action? Thank you again.

MarcCote commented 4 years ago

Yes. This is how you can do it.

options = textworld.GameOptions()
options.grammar.only_last_action = True

M = GameMaker(options)

EDIT: you can lookup the available grammar's options and their description here: https://textworld.readthedocs.io/en/stable/textworld.generator.grammar.html#textworld.generator.text_grammar.GrammarOptions

gari-marcos commented 4 years ago

Okay, that was so helpful! thank you so much!!

MarcCote commented 4 years ago

My pleasure. Thanks for giving TextWorld a try :). We are open to feedback especially about the GameMaker's API which is truly a work-in-progress.

gari-marcos commented 4 years ago

I've been looking through the docs of GameMaker but I haven't been able to figure out if there is a way to insert intermediate rewards during the quest. Is this possible? Thank you again!

MarcCote commented 4 years ago

The way you would do that is by defining multiple quests.

quest = M.set_quest_from_commands(commands)

# Adding a subquest.
from textworld.generator import Quest, Event
subgoal = Event(conditions={M.new_fact("open", kitchen_living_door)})
M.quests.append(Quest(win_events=[subgoal]))

Note the use of Quest and Event classes which allow you to define more complex quests.

MarcCote commented 4 years ago

Also, there's a PR about better documentation here: https://github.com/microsoft/TextWorld/pull/186/files

gari-marcos commented 4 years ago

Oh, I see, thank you again!

gari-marcos commented 4 years ago

Hello, I am trying to set a custom quest description but I don't find any way. Is there any way to change the description of the quest? For instance, the last command is go north but I want the quest to be I need you to go to the bathroom. Is there any way to specify that? I tried adding a descin the last subquest but it didn't work.


commands = ['open chest drawer','take key from chest drawer', 
            'unlock wooden door with key', 'open wooden door', 
            'go east', 'go north']

quest = M.set_quest_from_commands(commands)
subgoal1 = Event(conditions={M.new_fact("open", chest_drawer)})
subgoal2 = Event(conditions={M.new_fact("in", key, M.inventory)})
subgoal3 = Event(conditions={M.new_fact("open", bedroom_kitchen_door)})
subgoal4 = Event(conditions={M.new_fact("at", M.player, kitchen)})
subgoal5 = Event(conditions={M.new_fact("at", M.player, bathroom)})
M.quests.append(Quest(win_events=[subgoal1]))
M.quests.append(Quest(win_events=[subgoal2]))
M.quests.append(Quest(win_events=[subgoal3]))
M.quests.append(Quest(win_events=[subgoal4]))
M.quests.append(Quest(win_events=[subgoal5], desc='I need you to go to the bathroom'))

Thank you!

MarcCote commented 4 years ago

That's a bug! I will make a fix. Keep them coming :)

gari-marcos commented 4 years ago

Ok, thanks!

MarcCote commented 4 years ago

You can try pip install https://github.com/MarcCote/TextWorld/archive/fix_239.zip. If that works for you I'll make a patch release.

gari-marcos commented 4 years ago

I tried with that fix, however, when I play the game the quest description doesn't seem to appear.

MarcCote commented 4 years ago

Can you share your whole python script?

gari-marcos commented 4 years ago
import textworld
from textworld import GameMaker
from textworld import g_rng
g_rng.set_seed(20180329)

from textworld.generator import Quest, Event

options = textworld.GameOptions()
options.grammar.only_last_action = True

M = GameMaker(options)
kitchen = M.new_room("kitchen")
living_room = M.new_room("living room")
bedroom = M.new_room("bedroom")
bathroom = M.new_room("bathroom")
backyard = M.new_room("backyard")

bedroom_kitchen = M.connect(bedroom.east, kitchen.west)
kitchen_living = M.connect(kitchen.south, living_room.north)
kitchen_bathroom = M.connect(kitchen.north, bathroom.south)
kitchen_backyard = M.connect(kitchen.east, backyard.west)

M.set_player(bedroom)

bedroom_kitchen_door = M.new_door(bedroom_kitchen, name = "wooden door")
M.add_fact("locked", bedroom_kitchen_door)
kitchen_backyard_door = M.new_door(kitchen_backyard, name = "screen door")
M.add_fact("locked", kitchen_backyard_door)

key = M.new(type = 'k', name = "key")
M.add_fact("match", key, bedroom_kitchen_door)
chest_drawer = M.new(type = 'c', name = 'chest drawer')
M.add_fact('closed', chest_drawer)
chest_drawer.add(key)
bed = M.new(type = 's', name = 'bed')
tv = M.new(type = 's', name ='tv')
bedroom.add(bed, tv, chest_drawer)

couch = M.new(type = 's', name = 'couch')
table = M.new(type = 's', name = 'table')
living_room.add(table)
living_room.add(couch)

stove = M.new(type = 's', name = 'stove')
apple = M.new(type = 'f', name = 'apple')
refrigerator = M.new(type = 'c', name = 'refrigerator')
M.add_fact('closed', refrigerator)
kitchen.add(refrigerator)
kitchen.add(stove)
refrigerator.add(apple)

bath = M.new(type = 'c', name = "bath")
M.add_fact('open', bath)
toilet = M.new(type = 'c', name = 'toilet')
M.add_fact('open', toilet)
sink = M.new(type = 's', name = 'sink')
toothbrush = M.new(type = 'o', name = 'toothbrush')
sink.add(toothbrush)
bathroom.add(bath)
bathroom.add(toilet)
bathroom.add(sink)

bbq = M.new(type = 's', name = 'bbq')
backyard.add(bbq)

#M.render(interactive=True)

#M.generate_distractors(10)

#quest = M.record_quest()

commands = ['open chest drawer','take key from chest drawer', 
            'unlock wooden door with key', 'open wooden door', 
            'go east', 'go north']

quest = M.set_quest_from_commands(commands)
subgoal1 = Event(conditions={M.new_fact("open", chest_drawer)})
subgoal2 = Event(conditions={M.new_fact("in", key, M.inventory)})
subgoal3 = Event(conditions={M.new_fact("open", bedroom_kitchen_door)})
subgoal4 = Event(conditions={M.new_fact("at", M.player, kitchen)})
subgoal5 = Event(conditions={M.new_fact("at", M.player, bathroom)})
M.quests.append(Quest(win_events=[subgoal1]))
M.quests.append(Quest(win_events=[subgoal2]))
M.quests.append(Quest(win_events=[subgoal3]))
M.quests.append(Quest(win_events=[subgoal4]))
M.quests.append(Quest(win_events=[subgoal5]))
quest.desc='I need you to go to the bathroom'

M.set_walkthrough(commands)

print( " > ".join(quest.commands))
print("\n" + quest.desc)

if not M.validate():
    exit

game = M.build()
game_name = "./test_game_bathroom"
game_file = M.compile(game_name)

This is the script, as you can see the last action is to go north. However, when I play the game this is what I get:

Your objective is to attempt to take a trip south.

-= Bedroom =-
This might come as a shock to you, but you've just moved into a bedroom.

You scan the room, seeing a chest drawer. You can make out a bed. The bed is
typical. However, the bed, like an empty bed, has nothing on it. You bend down
to tie your shoe. When you stand up, you notice a tv. The tv is usual. But the
thing hasn't got anything on it.

There is a closed wooden door leading east.

It says the objective is to go south, I don't know why.

MarcCote commented 4 years ago

You can always overwrite the game's objective directly. game.objective = "I need you to go to the bathroom." after building the game.

gari-marcos commented 4 years ago

Yeah, I tried but I get the same result as above.

game = M.build()
game.objective = "I need you to go to the bathroom"
game_name = "./test_game_bathroom_1"
game_file = M.compile(game_name)
Your objective is to attempt to take a trip south.

-= Bedroom =-
This might come as a shock to you, but you've just moved into a bedroom.

You scan the room, seeing a chest drawer. You can make out a bed. The bed is
typical. However, the bed, like an empty bed, has nothing on it. You bend down
to tie your shoe. When you stand up, you notice a tv. The tv is usual. But the
thing hasn't got anything on it.

There is a closed wooden door leading east
MarcCote commented 4 years ago

Ok, I'll investigate more but the problem seems to be related to M.set_walkthrough(commands).

gari-marcos commented 4 years ago

It seems it has something to do with that script because I tried with this one and it's actually working:

commands = ['open chest drawer','take key from chest drawer', 
            'unlock wooden door with key', 'open wooden door', 
            'go east', 'open screen door', 'go east']

quest = M.set_quest_from_commands(commands)
subgoal1 = Event(conditions={M.new_fact("open", chest_drawer)})
subgoal2 = Event(conditions={M.new_fact("in", key, M.inventory)})
subgoal3 = Event(conditions={M.new_fact("open", bedroom_kitchen_door)})
subgoal4 = Event(conditions={M.new_fact("at", M.player, kitchen)})
subgoal5 = Event(conditions={M.new_fact("at", M.player, bathroom)})
subgoal6 = Event(conditions={M.new_fact("open",kitchen_backyard_door)})
subgoal7 = Event(conditions={M.new_fact("at", M.player, backyard)})
M.quests.append(Quest(win_events=[subgoal1]))
M.quests.append(Quest(win_events=[subgoal2]))
M.quests.append(Quest(win_events=[subgoal3]))
M.quests.append(Quest(win_events=[subgoal4]))
M.quests.append(Quest(win_events=[subgoal5]))
M.quests.append(Quest(win_events=[subgoal6]))
M.quests.append(Quest(win_events=[subgoal7]))

#quest.desc='I need you to go to the bathroom'

M.set_walkthrough(commands)

print( " > ".join(quest.commands))
print("\n" + quest.desc)

if not M.validate():
    exit

game = M.build()
game.objective = "I need you to go to the backyard"
I need you to go to the backyard

-= Bedroom =-
This might come as a shock to you, but you've just moved into a bedroom.

You scan the room, seeing a chest drawer. You can make out a bed. The bed is
typical. However, the bed, like an empty bed, has nothing on it. You bend down
to tie your shoe. When you stand up, you notice a tv. The tv is usual. But the
thing hasn't got anything on it.

There is a closed wooden door leading east.

However, when I arrive at the backyard, this is what I get:

-= Backyard =-
Look around you. Take it all in. It's not every day someone gets to be in a
backyard.

You can make out a bbq. But oh no! there's nothing on this piece of junk. Hm. Oh
well

There is an open screen door leading west.

Your score has just gone up by two points.

It gives two points instead of one, and the game doesn't finish.

MarcCote commented 4 years ago

I think you don't need quest = M.set_quest_from_commands(commands)

MarcCote commented 4 years ago

I'm assuming quest and subgoal7 are the same, hence the two points.

MarcCote commented 4 years ago

Also, at the moment, the agent has to finish all "subgoal" before the game ends. That will change with PR #235

gari-marcos commented 4 years ago

Ok, thank you so much! I just realized I had a subgoal the player had to be at the bathroom and I wasn't aware of it. Now, it's doing okay. Also, the two last points were because of the quest and the subgoal. Thank you very much!

MarcCote commented 4 years ago

I had time to investigate more your original issue (i.e., "Your objective is to attempt to take a trip south." instead of "Your objective is to attempt to take a trip north."). I fixed a bug we had in trying to find the "optimal" trajectory to solve a game, which was used to define the objective of the game when none is provided.

In your example, the optimal trajectory does indeed lead you to the bathroom by going north from the kitchen, but the bug was bringing the player back to the kitchen to satisfy a second time the subgoal #4.

I pushed the fix to the same PR, if you need it. Otherwise, it will be part of the next patch release.