Source code for pydialogue.environment.descriptions

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module contains the classes needed to describe an object/entity.
"""
import copy
import random

from ..state import kn_helpers


[docs] def permute_list_of_lists(lists, fixed_elements=None, random_gen=None): """ Permutes the elements of the individual lists contained in one main list. Parameters ---------- lists : list A list that contains multiple lists. fixed_elements : list, optional The indices that should not be permuted. random_gen : random.Random, optional A random number generator. It is used to shuffle the elements. Returns ------- lists : list The list of lists that have the permuted elements. """ if fixed_elements is None: fixed_elements = list() nonfixed_indices = [idx for idx in range(len(lists[0])) if idx not in fixed_elements] nonfixed_elements = [[lists[idx][idy] for idy in nonfixed_indices] for idx in range(len(lists))] random_gen.shuffle(nonfixed_indices) for idx in range(len(nonfixed_elements)): for idy in range(len(nonfixed_elements[idx])): lists[idx][nonfixed_indices[idy]] = nonfixed_elements[idx][idy] return lists
[docs] class BaseDescription: """ Generates the elements that are used to describe an entity. For example, the [size, color, type] might be used to describe an apple. Attributes ---------- item : BaseEntity or Entity The item that is described. mandatory : list A list of True-s and False-s indicating whether the elements (self.elements) used for the description are mandatory. True represents mandatory, and False represents optional. elements : list The list of elements that are used to describe the item. These can be properties and attributes of the item. These elements are selected from the list of candidates. cand_elements : list The list of candidate elements. The self.elements are selected from this list. cand_mandatory : list This list of True-s and False-s indicates whether the candidate elements are mandatory or not. random_gen : random.Random The random generator is used to pick the optional arguments from the list of candidate elements. """ def __init__(self, item=None, mandatory=None, elements=None, cand_elements=None, cand_mandatory=None, random_gen=None): self.item = item self.mandatory = list() if mandatory is None else mandatory self.elements = list() if elements is None else elements self.cand_elements = list() if cand_elements is None else cand_elements self.cand_mandatory = list() if cand_mandatory is None else cand_mandatory if cand_mandatory is None and cand_elements is not None: self.cand_mandatory = [False] * len(cand_elements) self.random_gen = random_gen def __eq__(self, other): """ Checks whether two descriptions are equal. """ result = False if isinstance(other, BaseDescription) and type(self) is type(other): if (len(self.elements) > 0 and self.elements == other.elements and len(self.mandatory) > 0 and self.mandatory == other.mandatory): result = True elif len(self.elements) == 0 and len(other.elements) == 0 and self.cand_elements == other.cand_elements: result = True return result
[docs] def mycopy(self): """ Shallow copies the members of the description. """ cls = self.__class__ result = cls.__new__(cls) setattr(result, 'item', self.item) setattr(result, 'mandatory', copy.copy(self.mandatory)) setattr(result, 'elements', copy.copy(self.elements)) setattr(result, 'cand_elements', copy.copy(self.cand_elements)) setattr(result, 'cand_mandatory', copy.copy(self.cand_mandatory)) setattr(result, 'random_gen', copy.copy(self.random_gen)) return result
[docs] def pick_random_elements(self): """ Randomly selects the optional description elements, and it removes the rest of the optional elements from the self.elements and self.mandatory lists. For this function to work, the fields self.elements and self.mandatory must already be filled. """ indices_included = [] for idx in range(len(self.mandatory)): if self.mandatory[idx] is True or (self.mandatory[idx] is False and self.random_gen.randint(0, 1) == 1): indices_included.append(idx) self.elements = [self.elements[idx] for idx in range(len(self.elements)) if idx in indices_included] self.mandatory = [self.mandatory[idx] for idx in range(len(self.mandatory)) if idx in indices_included]
[docs] def filter_candidate_elements(self, cand): """ Selects a set of elements from the list of candidate elements that uniquely describe the item. Priority is given to the elements that are already seen by the agent. The non-seen elements needed for the entity's description to be unique are kept to the minimum. Parameters ---------- cand : list The candidate is a list of two lists. The first list contains the elements, and the second one is a list of False-s and True-s, indicating whether you prefer the elements to be mandatory or not. If you do not have a preference you can leave all values False since in this function a check is done whether the optional arguments need to be mandatory anyway. Returns ------- elements : list A list of elements that are used for the entity's description mandatory : list A list of False-s and True-s indicating whether the element is mandatory or not (with True being mandatory). """ seen_indices = [] not_seen_indices = [] for idx, el in enumerate(cand[0]): if el in self.item.prop_seen or el in self.item.attr_seen: seen_indices.append(idx) else: not_seen_indices.append(idx) new_cand = [[], []] for idx in seen_indices+not_seen_indices: new_cand[0].append(cand[0][idx]) new_cand[1].append(cand[1][idx]) cand = new_cand elements = [] mandatory = [] unique = False first_unique = True similar_objs = self.item.world.obj_list for idx, el in enumerate(cand[0]): if el in self.item.properties or el in self.item.attributes: elements.append(el) # open and locked are changeable attributes. if el not in ['open', 'locked']: if not unique: similar_objs = kn_helpers.find_similar_objs(similar_objs, self.item, el) if len(similar_objs) == 1 and similar_objs[0] == self.item: unique = True mandatory.append(cand[1][idx] is True or not unique or (unique and first_unique)) if unique and first_unique: first_unique = False if unique is False: return [], [] new_el = [] new_mand = [] for idx, el in enumerate(elements): if el in self.item.prop_seen or el in self.item.attr_seen: new_el.append(el) new_mand.append(mandatory[idx]) else: # do not reveal too much information at once # if the element is not revealed if mandatory[idx] is False: #and self.random_gen.choices([True, False], weights=[0.8, 0.2], k=1)[0] is True: break else: new_el.append(el) new_mand.append(mandatory[idx]) # check if deleting some elements helps elements = new_el mandatory = new_mand """ new_el = [] new_mand = [] similar_objs = self.item.world.obj_list for idx, el in reversed(list(enumerate(elements))): if mandatory[idx] is True: new_el.append(el) new_mand.append(True) similar_objs = kn_helpers.find_similar_objs(similar_objs, self.item, el) if len(similar_objs) == 1 and similar_objs[0] == self.item: break elements = new_el mandatory = new_mand """ return elements, mandatory
[docs] def generate_description(self, random_pick=True): """ Generates and updates the self.elements and self.mandatory if they are empty. Additionally, it can pick which optional elements from the self.elements list to be included. """ if len(self.elements) == 0: self.generate_elements() if random_pick: self.pick_random_elements() if 'abstract' in self.item.attributes: if 'the' in self.elements: idx = self.elements.index('the') del self.elements[idx] del self.mandatory[idx] if 'abstract' in self.elements: idx = self.elements.index('abstract') del self.elements[idx] del self.mandatory[idx] article = "a" first_elem = None if self.elements[0] in self.item.attributes: first_elem = self.elements[0] elif self.elements[0] in self.item.properties: first_elem = self.item.properties[self.elements[0]] if first_elem is not None: if isinstance(first_elem, (tuple, list)): first_elem_str = " ".join(first_elem) else: first_elem_str = first_elem if first_elem_str.startswith(('a', 'e', 'i', 'o', 'u')): article = "an" self.elements.insert(0, article) self.mandatory.insert(0, True) else: if 'name' in self.elements and 'player' not in self.item.attributes: self.elements.insert(0, 'the') self.mandatory.insert(0, True) if len(self.elements) > 0 and all([prop not in self.elements for prop in ['name', 'nickname', 'surname']]): if 'type' not in self.elements and 'entity' not in self.elements: self.elements.append('entity') self.mandatory.append(True) if 'the' not in self.elements and 'abstract' not in self.item.attributes: self.elements.insert(0, 'the') self.mandatory.insert(0, True) if 'type' in self.elements and 'entity' in self.elements: idx = self.elements.index('entity') del self.elements[idx] del self.mandatory[idx] if 'type' in self.elements: idx = self.elements.index('type') self.elements.remove('type') self.elements.append('type') mand_val = self.mandatory[idx] del self.mandatory[idx] self.mandatory.append(mand_val)
[docs] def generate_elements(self): """ Generates the elements and the mandatory list from the list of candidate elements.""" self.elements, self.mandatory = self.filter_candidate_elements(permute_list_of_lists([self.cand_elements, self.cand_mandatory], random_gen=self.random_gen))