Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion textworld/envs/glulx/git_glulx_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ def command_feedback(self):
"""
if not hasattr(self, "_command_feedback"):
command_feedback = self.feedback

# On the first move, command_feedback should be empty.
if self.nb_moves == 0:
command_feedback = ""

# Remove room description from command feedback.
if len(self.description.strip()) > 0:
regex = "\s*" + re.escape(self.description.strip()) + "\s*"
Expand Down Expand Up @@ -531,7 +536,7 @@ def render(self, mode: str = "human") -> None:
# Wrap each paragraph.
if mode == "human":
paragraphs = msg.split("\n")
paragraphs = ["\n".join(textwrap.wrap(paragraph, width=80)) for paragraph in paragraphs]
paragraphs = ["\n".join(textwrap.wrap(paragraph, width=120)) for paragraph in paragraphs]
msg = "\n".join(paragraphs)

outfile.write(msg + "\n")
Expand Down
3 changes: 2 additions & 1 deletion textworld/envs/glulx/tests/test_git_glulx_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ def build_test_game():
path.door.add_property("open")

carrot = M.new(type='f', name='carrot')
M.add_fact("edible", carrot)
M.inventory.add(carrot)

# Add a closed chest in R2.
chest = M.new(type='c', name='chest')
chest = M.new(type='chest', name='chest')
chest.add_property("open")
R2.add(chest)

Expand Down
13 changes: 8 additions & 5 deletions textworld/generator/chaining.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.


import re
from collections import Counter
from functools import total_ordering
from numpy.random import RandomState
Expand Down Expand Up @@ -103,7 +103,7 @@ def __init__(self):
self.subquests = False
self.independent_chains = False
self.create_variables = False
self.fixed_mapping = data.get_types().constants_mapping
self.fixed_mapping = {Placeholder(t.name): Variable(t.name) for t in data.get_types() if t.constant}
self.rng = None
self.logic = data.get_logic()
self.rules_per_depth = []
Expand Down Expand Up @@ -365,9 +365,9 @@ def check_action(self, node: _Node, state: State, action: Action) -> bool:
while nav_parent.action is not None and self._is_navigation(nav_parent.action):
# HACK: Going through a door is always considered navigation unless the previous action was to open that door.
parent = nav_parent.parent
if parent.action is not None and parent.action.name == "open/d":
if parent.action is not None and self._is_open_door(parent.action):
break
if self.backward and action.name == "open/d":
if self.backward and self._is_open_door(action):
break
nav_parent = parent

Expand All @@ -389,7 +389,10 @@ def check_action(self, node: _Node, state: State, action: Action) -> bool:
return self.options.check_action(state, action)

def _is_navigation(self, action):
return action.name.startswith("go/")
return re.match("go\(.*\).*", action.name)

def _is_open_door(self, action):
return re.match("open\(d\).*", action.name)

def apply(self, node: _Node, action: Action) -> Optional[State]:
"""Attempt to apply an action to the given state."""
Expand Down
66 changes: 55 additions & 11 deletions textworld/generator/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from typing import Optional

from textworld.logic import GameLogic
from textworld.generator.vtypes import VariableType, VariableTypeTree
from textworld.utils import maybe_mkdir, RegexDict

BUILTIN_DATA_PATH = os.path.dirname(__file__)
Expand Down Expand Up @@ -82,17 +81,17 @@ def create_data_files(dest: str = './textworld_data', verbose: bool = False, for
INFORM7_ADDONS_CODE = None


def _to_type_tree(types):
vtypes = []
# def _to_type_tree(types):
# vtypes = []

for vtype in sorted(types):
if vtype.parents:
parent = vtype.parents[0]
else:
parent = None
vtypes.append(VariableType(vtype.name, vtype.name, parent))
# for vtype in sorted(types):
# if vtype.parents:
# parent = vtype.parents[0]
# else:
# parent = None
# vtypes.append(VariableType(vtype.name, vtype.name, parent))

return VariableTypeTree(vtypes)
# return VariableTypeTree(vtypes)


def _to_regex_dict(rules):
Expand Down Expand Up @@ -134,7 +133,8 @@ def load_logic(target_dir: str):
logic = GameLogic.load(paths)

global _TYPES
_TYPES = _to_type_tree(logic.types)
# _TYPES = _to_type_tree(logic.types)
_TYPES = logic.types

global _RULES
_RULES = _to_regex_dict(logic.rules.values())
Expand Down Expand Up @@ -197,13 +197,57 @@ def get_constraints():


def get_reverse_rules(action):
assert False, "deprecated" # XXX
return _REVERSE_RULES(action)


def get_reverse_action(action):
r_action = action.inverse()
for rule in get_rules().values():
r_action.name = rule.name
if rule.match(r_action):
return r_action

return None


def get_types():
return _TYPES



def sample_type(parent_type, rng, exceptions=[], include_parent=True, probs=None):
""" Sample an object type given the parent's type. """
import numpy as np
types = [t.name for t in get_types().get(parent_type).descendants]
if include_parent:
types = [parent_type] + types
types = [t for t in types if t not in exceptions]

if probs is not None:
probs = np.array([probs[t] for t in types], dtype="float")
probs /= np.sum(probs)

return rng.choice(types, p=probs)


def count_types(state):
""" Counts how many objects there are of each type. """
types_counts = {t.name: 0 for t in get_types()}
for var in state.variables:
if get_types().get(var.type).constant:
continue

if "_" not in var.name:
continue

cpt = int(var.name.split("_")[-1])
var_type = var.type
types_counts[var_type] = max(cpt + 1, types_counts[var_type])

return types_counts


def get_data_path():
return _DATA_PATH

Expand Down
31 changes: 31 additions & 0 deletions textworld/generator/data/logic/box.twl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# A type of container that is portable.
type box : t, container, portable, openable {
predicates {
reachable_contents(box) = reachable(box) & open(box);
reachable(box) = in(box, I);
reachable(box) = at(P, r) & at(box, r);
reachable(box) = reachable(table) & on(box, table);
reachable(box) = reachable_contents(chest) & in(box, chest);
}

constraints {
no_nested_boxes :: in(x: box, y: box) -> fail();
no_box_on_stool :: on(box, stool) -> fail();
}

inform7 {
type {
kind :: "box-like";
}

code :: """
[Avoid nesting box-like objects because of some limitation with alias cycles.]
Instead of inserting a box-like (called source) into a box-like (called dest):
say "You cannot insert [the source] in [the dest]!";

Instead of putting a box-like (called source) on a stool-like (called dest):
say "You cannot put [the source] on [the dest]!";
""";

}
}
21 changes: 21 additions & 0 deletions textworld/generator/data/logic/chest.twl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# A type of container that is fixed in place and lockable.
type chest : t, container, fixed_in_place, lockable {
predicates {
reachable_contents(chest) = reachable(chest) & open(chest);
}

inform7 {
type {
kind :: "chest-like";
}
}
}

## Alias for backward compatibility.
#type c : chest {
# inform7 {
# type {
# kind :: "old-c";
# }
# }
#}
47 changes: 6 additions & 41 deletions textworld/generator/data/logic/container.twl
Original file line number Diff line number Diff line change
@@ -1,52 +1,17 @@
# container
type c : t {
# Property of an object that can contain other objects.
type container {
predicates {
open(c);
closed(c);
locked(c);

in(o, c);
}

rules {
lock/c :: $at(P, r) & $at(c, r) & $in(k, I) & $match(k, c) & closed(c) -> locked(c);
unlock/c :: $at(P, r) & $at(c, r) & $in(k, I) & $match(k, c) & locked(c) -> closed(c);

open/c :: $at(P, r) & $at(c, r) & closed(c) -> open(c);
close/c :: $at(P, r) & $at(c, r) & open(c) -> closed(c);
}

reverse_rules {
lock/c :: unlock/c;
open/c :: close/c;
}

constraints {
c1 :: open(c) & closed(c) -> fail();
c2 :: open(c) & locked(c) -> fail();
c3 :: closed(c) & locked(c) -> fail();
in(portable, container);
}

inform7 {
type {
kind :: "container";
definition :: "containers are openable, lockable and fixed in place. containers are usually closed.";
kind :: "";
definition :: "It is a kind of container.";
}

predicates {
open(c) :: "The {c} is open";
closed(c) :: "The {c} is closed";
locked(c) :: "The {c} is locked";

in(o, c) :: "The {o} is in the {c}";
}

commands {
open/c :: "open {c}" :: "opening the {c}";
close/c :: "close {c}" :: "closing the {c}";

lock/c :: "lock {c} with {k}" :: "locking the {c} with the {k}";
unlock/c :: "unlock {c} with {k}" :: "unlocking the {c} with the {k}";
in(portable, container) :: "The {portable} is in the {container}";
}
}
}
53 changes: 27 additions & 26 deletions textworld/generator/data/logic/door.twl
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
# door
type d : t {
type d : t, lockable {
predicates {
open(d);
closed(d);
locked(d);

link(r, d, r);

north_of/d(r, d, r);
west_of/d(r, d, r);

south_of/d(r, d, r') = north_of/d(r', d, r);
east_of/d(r, d, r') = west_of/d(r', d, r);

reachable(d) = at(P, r) & link(r, d, r') & link(r', d, r);
}

rules {
lock/d :: $at(P, r) & $link(r, d, r') & $link(r', d, r) & $in(k, I) & $match(k, d) & closed(d) -> locked(d);
unlock/d :: $at(P, r) & $link(r, d, r') & $link(r', d, r) & $in(k, I) & $match(k, d) & locked(d) -> closed(d);
lock(d, k) :: $reachable(d) & $in(k, I) & $match(k, d) & closed(d) -> locked(d);
unlock(d, k) :: $reachable(d) & $in(k, I) & $match(k, d) & locked(d) -> closed(d);

open/d :: $at(P, r) & $link(r, d, r') & $link(r', d, r) & closed(d) -> open(d) & free(r, r') & free(r', r);
close/d :: $at(P, r) & $link(r, d, r') & $link(r', d, r) & open(d) & free(r, r') & free(r', r) -> closed(d);
open(d) :: $reachable(d) & closed(d) -> open(d) & free(r, r') & free(r', r);
close(d) :: $reachable(d) & open(d) & free(r, r') & free(r', r) -> closed(d);
}

reverse_rules {
lock/d :: unlock/d;
open/d :: close/d;
lock(d, k) :: unlock(d, k);
open(d) :: close(d);
}

constraints {
d1 :: open(d) & closed(d) -> fail();
d2 :: open(d) & locked(d) -> fail();
d3 :: closed(d) & locked(d) -> fail();

# A door can't be used to link more than two rooms.
link1 :: link(r, d, r') & link(r, d, r'') -> fail();
link2 :: link(r, d, r') & link(r'', d, r''') -> fail();
Expand All @@ -35,7 +35,7 @@ type d : t {

# There cannot be more than four doors in a room.
too_many_doors :: link(r, d1: d, r1: r) & link(r, d2: d, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();

# There cannot be more than four doors in a room.
dr1 :: free(r, r1: r) & link(r, d2: d, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
dr2 :: free(r, r1: r) & free(r, r2: r) & link(r, d3: d, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail();
Expand All @@ -52,18 +52,19 @@ type d : t {
definition :: "door is openable and lockable.";
}

predicates {
open(d) :: "The {d} is open";
closed(d) :: "The {d} is closed";
locked(d) :: "The {d} is locked";
}

commands {
open/d :: "open {d}" :: "opening {d}";
close/d :: "close {d}" :: "closing {d}";
open(d) :: "open {d}" :: "opening {d}";
close(d) :: "close {d}" :: "closing {d}";

unlock/d :: "unlock {d} with {k}" :: "unlocking {d} with the {k}";
lock/d :: "lock {d} with {k}" :: "locking {d} with the {k}";
unlock(d, k) :: "unlock {d} with {k}" :: "unlocking {d} with the {k}";
lock(d, k) :: "lock {d} with {k}" :: "locking {d} with the {k}";
}

predicates {
north_of/d(r, d, r') :: "South of {r} and north of {r'} is a door called {d}";
south_of/d(r, d, r') :: "North of {r} and south of {r'} is a door called {d}";
east_of/d(r, d, r') :: "West of {r} and east of {r'} is a door called {d}";
west_of/d(r, d, r') :: "East of {r} and west of {r'} is a door called {d}";
}
}
}
Loading