diff --git a/textworld/challenges/spaceship/agent_design_a2c.py b/textworld/challenges/spaceship/agent_design_a2c.py new file mode 100644 index 00000000..bc78804a --- /dev/null +++ b/textworld/challenges/spaceship/agent_design_a2c.py @@ -0,0 +1,343 @@ +from collections import defaultdict +from os.path import join as pjoin +from time import time +from glob import glob +from typing import Mapping, Any, Optional +import re +import numpy as np + +import os +import gym + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch import optim + +from textworld import EnvInfos +import textworld.gym + + +PATH = pjoin(os.path.dirname(__file__), 'textworld_data') +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +class ActorzCritic(nn.Module): + + eps = 0.01 + + def __init__(self, input_size, hidden_size): + super(ActorzCritic, self).__init__() + torch.manual_seed(42) # For reproducibility + self.embedding = nn.Embedding(input_size, hidden_size) + self.encoder_gru = nn.GRU(hidden_size, hidden_size) + self.cmd_encoder_gru = nn.GRU(hidden_size, hidden_size) + self.state_gru = nn.GRU(hidden_size, hidden_size) + + self.linear_1 = nn.Linear(2 * hidden_size, 2 * hidden_size) + self.critic = nn.Linear(hidden_size, 1) + self.actor = nn.Linear(hidden_size * 2, 1) + + # Parameters + self.state_hidden = torch.zeros(1, 1, hidden_size, device=device) + self.hidden_size = hidden_size + + def forward(self, obs, commands, mode, method): + input_length, batch_size = obs.size(0), obs.size(1) + nb_cmds = commands.size(1) + + embedded = self.embedding(obs) + encoder_output, encoder_hidden = self.encoder_gru(embedded) + + state_output, state_hidden = self.state_gru(encoder_hidden, self.state_hidden) + self.state_hidden = state_hidden + state_value = self.critic(state_output) + + # Attention network over the commands. + cmds_embedding = self.embedding.forward(commands) + _, cmds_encoding_last_states = self.cmd_encoder_gru.forward(cmds_embedding) # 1*cmds*hidden + + # Same observed state for all commands. + cmd_selector_input = torch.stack([state_hidden] * nb_cmds, 2) # 1*batch*cmds*hidden + + # Same command choices for the whole batch. + cmds_encoding_last_states = torch.stack([cmds_encoding_last_states] * batch_size, 1) # 1*batch*cmds*hidden + + # Concatenate the observed state and command encodings. + input_ = torch.cat([cmd_selector_input, cmds_encoding_last_states], dim=-1) + + # One FC layer + x = F.relu(self.linear_1(input_)) + + # Compute state-action value (score) per command. + action_state = F.relu(self.actor(x)).squeeze(-1) # 1 x Batch x cmds + # action_state = F.relu(self.actor(input_)).squeeze(-1) # 1 x Batch x cmds + + probs = F.softmax(action_state, dim=2) # 1 x Batch x cmds + + if mode == "train": + action_index = probs[0].multinomial(num_samples=1).unsqueeze(0) # 1 x batch x indx + elif mode == "test": + if method == 'random': + action_index = probs[0].multinomial(num_samples=1).unsqueeze(0) # 1 x batch x indx + elif method == 'arg-max': + action_index = probs[0].max(1).indices.unsqueeze(-1).unsqueeze(-1) # 1 x batch x indx + elif method == 'eps-soft': + index = probs[0].max(1).indices.unsqueeze(-1).unsqueeze(-1) + p = np.random.random() + if p < (1 - self.eps + self.eps / nb_cmds): + action_index = index + else: + while True: + tp = np.random.choice(probs[0][0].detach().numpy()) + if (probs[0][0] == tp).nonzero().unsqueeze(-1) != index: + action_index = (probs[0][0] == tp).nonzero().unsqueeze(-1) + break + + return action_state, action_index, state_value + + def reset_hidden(self, batch_size): + self.state_hidden = torch.zeros(1, batch_size, self.hidden_size, device=device) + + +class NeuralAgent: + """ Simple Neural Agent for playing TextWorld games. """ + + MAX_VOCAB_SIZE = 1000 + UPDATE_FREQUENCY = 10 + LOG_FREQUENCY = 1000 + GAMMA = 0.9 + + def __init__(self) -> None: + self.id2word = ["", ""] + self.word2id = {w: i for i, w in enumerate(self.id2word)} + + self.model = ActorzCritic(input_size=self.MAX_VOCAB_SIZE, hidden_size=128) + self.optimizer = optim.Adam(self.model.parameters(), 0.00003) + + def train(self): + self.mode = "train" + self.method = "random" + self.transitions = [] + self.last_score = 0 + self.no_train_step = 0 + self.stats = {"max": defaultdict(list), "mean": defaultdict(list)} + self.memo = {"max": defaultdict(list), "mean": defaultdict(list), "mem": defaultdict(list)} + self.model.reset_hidden(1) + + def test(self, method): + self.mode = "test" + self.method = method + self.model.reset_hidden(1) + + @property + def infos_to_request(self) -> EnvInfos: + return EnvInfos(description=True, inventory=True, admissible_commands=True, has_won=True, has_lost=True) + + def act(self, obs: str, score: int, done: bool, infos: Mapping[str, Any]) -> Optional[str]: + # Build agent's observation: feedback + look + inventory. + input_ = "{}\n{}\n{}".format(obs, infos["description"], infos["inventory"]) + + # Tokenize and pad the input and the commands to chose from. + input_tensor = self._process([input_]) + commands_tensor = self._process(infos["admissible_commands"]) + + # Get our next action and value prediction. + outputs, indexes, values = self.model(input_tensor, commands_tensor, mode=self.mode, method=self.method) + action = infos["admissible_commands"][indexes[0]] + + if self.mode == "test": + if done: + self.model.reset_hidden(1) + return action + + self.no_train_step += 1 + + if self.transitions: + reward = score - self.last_score # Reward is the gain/loss in score. + self.last_score = score + if infos["has_won"]: + reward += 100 + if infos["has_lost"]: + reward -= 100 + + self.transitions[-1][0] = reward # Update reward information. + + self.stats["max"]["score"].append(score) + self.memo["max"]["score"].append(score) + + if self.no_train_step % self.UPDATE_FREQUENCY == 0: + # Update model + returns, advantages = self._discount_rewards(values) + + loss = 0 + for transition, ret, advantage in zip(self.transitions, returns, advantages): + reward, indexes_, outputs_, values_ = transition + + advantage = advantage.detach() # Block gradients flow here. + probs = F.softmax(outputs_, dim=2) + log_probs = torch.log(probs) + log_action_probs = log_probs.gather(2, indexes_) + policy_loss = (log_action_probs * advantage).sum() + value_loss = ((values_ - ret) ** 2.).sum() + entropy = (-probs * log_probs).sum() + loss += 0.5 * value_loss - policy_loss - 0.001 * entropy + + self.memo["mem"]["selected_action_index"].append(indexes_.item()) + self.memo["mem"]["state_val_func"].append(values_.item()) + self.memo["mem"]["advantage"].append(advantage.item()) + self.memo["mem"]["return"].append(ret.item()) + self.memo["mean"]["reward"].append(reward) + self.memo["mean"]["policy_loss"].append(policy_loss.item()) + self.memo["mean"]["value_loss"].append(value_loss.item()) + + self.stats["mean"]["reward"].append(reward) + self.stats["mean"]["policy_loss"].append(policy_loss.item()) + self.stats["mean"]["value_loss"].append(value_loss.item()) + self.stats["mean"]["entropy"].append(entropy.item()) + self.stats["mean"]["confidence"].append(torch.exp(log_action_probs).item()) + + if self.no_train_step % self.LOG_FREQUENCY == 0: + msg = "{}. ".format(self.no_train_step) + msg += " ".join("{}: {:.3f}".format(k, np.mean(v)) for k, v in self.stats["mean"].items()) + msg += " " + " ".join("{}: {}".format(k, np.max(v)) for k, v in self.stats["max"].items()) + msg += " vocab: {}".format(len(self.id2word)) + print(msg) + self.stats = {"max": defaultdict(list), "mean": defaultdict(list)} + + self.optimizer.zero_grad() + loss.backward() + nn.utils.clip_grad_norm(self.model.parameters(), 40) + self.optimizer.step() + self.optimizer.zero_grad() + + self.transitions = [] + self.model.reset_hidden(1) + else: + # Keep information about transitions for Truncated Backpropagation Through Time. + self.transitions.append([None, indexes, outputs, values]) # Reward will be set on the next call + + if done: + self.last_score = 0 # Will be starting a new episode. Reset the last score. + + return action + + def _process(self, texts): + texts = list(map(self._tokenize, texts)) + max_len = max(len(l) for l in texts) + padded = np.ones((len(texts), max_len)) * self.word2id[""] + + for i, text in enumerate(texts): + padded[i, :len(text)] = text + + padded_tensor = torch.from_numpy(padded).type(torch.long).to(device) + padded_tensor = padded_tensor.permute(1, 0) # Batch x Seq => Seq x Batch + return padded_tensor + + def _tokenize(self, text): + # Simple tokenizer: strip out all non-alphabetic characters. + text = re.sub("[^a-zA-Z0-9\- ]", " ", text) + word_ids = list(map(self._get_word_id, text.split())) + return word_ids + + def _get_word_id(self, word): + if word not in self.word2id: + if len(self.word2id) >= self.MAX_VOCAB_SIZE: + return self.word2id[""] + + self.id2word.append(word) + self.word2id[word] = len(self.word2id) + + return self.word2id[word] + + def _discount_rewards(self, last_values): + returns, advantages = [], [] + R = last_values.data + for t in reversed(range(len(self.transitions))): + rewards, _, _, values = self.transitions[t] + R = rewards + self.GAMMA * R + adv = R - values + returns.append(R) + advantages.append(adv) + + return returns[::-1], advantages[::-1] + + +def play(agent, path, max_step=50, nb_episodes=10, verbose=True): + """ + This code uses the cooking agent design in the spaceship game. + + :param agent: the obj of NeuralAgent, a sample object for the agent + :param path: The path to the game (envo model) + """ + + infos_to_request = agent.infos_to_request + infos_to_request.max_score = True # Needed to normalize the scores. + + gamefiles = [path] + if os.path.isdir(path): + gamefiles = glob(os.path.join(path, "*.ulx")) + + env_id = textworld.gym.register_games(gamefiles, + request_infos=infos_to_request, + max_episode_steps=max_step) + env = gym.make(env_id) # Create a Gym environment to play the text game. + + if verbose: + if os.path.isdir(path): + print(os.path.dirname(path), end="") + else: + print(os.path.basename(path), end="") + + # Collect some statistics: nb_steps, final reward. + avg_moves, avg_scores, avg_norm_scores, seed_h = [], [], [], 4567 + for no_episode in range(nb_episodes): + obs, infos = env.reset() # Start new episode. + + env.env.textworld_env._wrapped_env.seed(seed=seed_h) + seed_h += 1 + + score = 0 + done = False + nb_moves = 0 + while not done: + command = agent.act(obs, score, done, infos) + obs, score, done, infos = env.step(command) + nb_moves += 1 + agent.act(obs, score, done, infos) # Let the agent know the game is done. + + if verbose: + print(".", end="") + avg_moves.append(nb_moves) + avg_scores.append(score) + avg_norm_scores.append(score / infos["max_score"]) + + env.close() + msg = " \tavg. steps: {:5.1f}; avg. score: {:4.1f} / {}." + if verbose: + if os.path.isdir(path): + print(msg.format(np.mean(avg_moves), np.mean(avg_norm_scores), 1)) + else: + print(avg_scores) + print(msg.format(np.mean(avg_moves), np.mean(avg_scores), infos["max_score"])) + + +agent = NeuralAgent() +step_size = 750 + +print(" ===== Training ===================================================== ") +agent.train() # Tell the agent it should update its parameters. +start_time = time() +print(os.path.realpath("./games/levelMedium_v1.ulx")) +play(agent, "./games/levelMedium_v1.ulx", max_step=step_size, nb_episodes=2000, verbose=False) +print("Trained in {:.2f} secs".format(time() - start_time)) + +print(' ===== Test ========================================================= ') +agent.test(method='random') +play(agent, "./games/levelMedium_v1.ulx", max_step=step_size) # Medium level game. + +save_path = "./model/levelMedium_v1_random.npy" +if not os.path.exists(os.path.dirname(save_path)): + os.mkdir(os.path.dirname(save_path)) + +np.save(save_path, agent) diff --git a/textworld/challenges/spaceship/build_agent_TW_tutorial.py b/textworld/challenges/spaceship/build_agent_TW_tutorial.py new file mode 100644 index 00000000..1fc03fae --- /dev/null +++ b/textworld/challenges/spaceship/build_agent_TW_tutorial.py @@ -0,0 +1,298 @@ +import os +import gym +import re +import argparse +import numpy as np +from typing import Mapping, Any, Optional +from glob import glob +from os.path import join as pjoin +from time import time +from collections import defaultdict + +import textworld.gym +from textworld.generator.data import KnowledgeBase +from textworld.challenges import register +from textworld.challenges.spaceship.maker import spaceship_maker_level_medium_v1 +from textworld import EnvInfos + +import torch +import torch.nn as nn +from torch import optim +import torch.nn.functional as F + +PATH = pjoin(os.path.dirname(__file__), 'textworld_data') + + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + +class CommandScorer(nn.Module): + def __init__(self, input_size, hidden_size): + super(CommandScorer, self).__init__() + torch.manual_seed(42) # For reproducibility + self.embedding = nn.Embedding(input_size, hidden_size) + self.encoder_gru = nn.GRU(hidden_size, hidden_size) + self.cmd_encoder_gru = nn.GRU(hidden_size, hidden_size) + self.state_gru = nn.GRU(hidden_size, hidden_size) + self.hidden_size = hidden_size + self.state_hidden = torch.zeros(1, 1, hidden_size, device=device) + self.critic = nn.Linear(hidden_size, 1) + self.att_cmd = nn.Linear(hidden_size * 2, 1) + + def reset_hidden(self, batch_size): + self.state_hidden = torch.zeros(1, batch_size, self.hidden_size, device=device) + + def forward(self, obs, commands, **kwargs): + input_length = obs.size(0) + batch_size = obs.size(1) + nb_cmds = commands.size(1) + + embedded = self.embedding(obs) + encoder_output, encoder_hidden = self.encoder_gru(embedded) + state_output, state_hidden = self.state_gru(encoder_hidden, self.state_hidden) + self.state_hidden = state_hidden + value = self.critic(state_output) + + # Attention network over the commands. + cmds_embedding = self.embedding.forward(commands) + _, cmds_encoding_last_states = self.cmd_encoder_gru.forward(cmds_embedding) # 1 x cmds x hidden + + # Same observed state for all commands. + cmd_selector_input = torch.stack([state_hidden] * nb_cmds, 2) # 1 x batch x cmds x hidden + + # Same command choices for the whole batch. + cmds_encoding_last_states = torch.stack([cmds_encoding_last_states] * batch_size, 1) # 1 x batch x cmds x hidden + + # Concatenate the observed state and command encodings. + cmd_selector_input = torch.cat([cmd_selector_input, cmds_encoding_last_states], dim=-1) + + # Compute one score per command. + scores = F.relu(self.att_cmd(cmd_selector_input)).squeeze(-1) # 1 x Batch x cmds + + probs = F.softmax(scores, dim=2) # 1 x Batch x cmds + index = probs[0].multinomial(num_samples=1).unsqueeze(0) # 1 x batch x indx + return scores, index, value + + +class NeuralAgent: + """ Simple Neural Agent for playing TextWorld games. """ + MAX_VOCAB_SIZE = 1000 + UPDATE_FREQUENCY = 10 + LOG_FREQUENCY = 1000 + GAMMA = 0.9 + + def __init__(self) -> None: + self._initialized = False + self._epsiode_has_started = False + self.id2word = ["", ""] + self.word2id = {w: i for i, w in enumerate(self.id2word)} + + self.model = CommandScorer(input_size=self.MAX_VOCAB_SIZE, hidden_size=128) + self.optimizer = optim.Adam(self.model.parameters(), 0.00003) + + self.mode = "test" + + def train(self): + self.mode = "train" + self.stats = {"max": defaultdict(list), "mean": defaultdict(list)} + self.transitions = [] + self.model.reset_hidden(1) + self.last_score = 0 + self.no_train_step = 0 + + def test(self): + self.mode = "test" + self.model.reset_hidden(1) + + @property + def infos_to_request(self) -> EnvInfos: + return EnvInfos(description=True, inventory=True, admissible_commands=True, has_won=True, has_lost=True) + + def act(self, obs: str, score: int, done: bool, infos: Mapping[str, Any]) -> Optional[str]: + # Build agent's observation: feedback + look + inventory. + input_ = "{}\n{}\n{}".format(obs, infos["description"], infos["inventory"]) + + # Tokenize and pad the input and the commands to chose from. + input_tensor = self._process([input_]) + commands_tensor = self._process(infos["admissible_commands"]) + + # Get our next action and value prediction. + outputs, indexes, values = self.model(input_tensor, commands_tensor) + action = infos["admissible_commands"][indexes[0]] + + if self.mode == "test": + if done: + self.model.reset_hidden(1) + return action + + self.no_train_step += 1 + + if self.transitions: + reward = score - self.last_score # Reward is the gain/loss in score. + self.last_score = score + if infos["has_won"]: + reward += 100 + if infos["has_lost"]: + reward -= 100 + + self.transitions[-1][0] = reward # Update reward information. + + self.stats["max"]["score"].append(score) + if self.no_train_step % self.UPDATE_FREQUENCY == 0: + # Update model + returns, advantages = self._discount_rewards(values) + + loss = 0 + for transition, ret, advantage in zip(self.transitions, returns, advantages): + reward, indexes_, outputs_, values_ = transition + + advantage = advantage.detach() # Block gradients flow here. + probs = F.softmax(outputs_, dim=2) + log_probs = torch.log(probs) + log_action_probs = log_probs.gather(2, indexes_) + policy_loss = (-log_action_probs * advantage).sum() + value_loss = (.5 * (values_ - ret) ** 2.).sum() + entropy = (-probs * log_probs).sum() + loss += policy_loss + 0.5 * value_loss - 0.1 * entropy + + self.stats["mean"]["reward"].append(reward) + self.stats["mean"]["policy"].append(policy_loss.item()) + self.stats["mean"]["value"].append(value_loss.item()) + self.stats["mean"]["entropy"].append(entropy.item()) + self.stats["mean"]["confidence"].append(torch.exp(log_action_probs).item()) + + if self.no_train_step % self.LOG_FREQUENCY == 0: + msg = "{}. ".format(self.no_train_step) + msg += " ".join("{}: {:.3f}".format(k, np.mean(v)) for k, v in self.stats["mean"].items()) + msg += " " + " ".join("{}: {}".format(k, np.max(v)) for k, v in self.stats["max"].items()) + msg += " vocab: {}".format(len(self.id2word)) + print(msg) + self.stats = {"max": defaultdict(list), "mean": defaultdict(list)} + + loss.backward() + nn.utils.clip_grad_norm_(self.model.parameters(), 40) + self.optimizer.step() + self.optimizer.zero_grad() + + self.transitions = [] + self.model.reset_hidden(1) + else: + # Keep information about transitions for Truncated Backpropagation Through Time. + self.transitions.append([None, indexes, outputs, values]) # Reward will be set on the next call + + if done: + self.last_score = 0 # Will be starting a new episode. Reset the last score. + + return action + + def _process(self, texts): + texts = list(map(self._tokenize, texts)) + max_len = max(len(l) for l in texts) + padded = np.ones((len(texts), max_len)) * self.word2id[""] + + for i, text in enumerate(texts): + padded[i, :len(text)] = text + + padded_tensor = torch.from_numpy(padded).type(torch.long).to(device) + padded_tensor = padded_tensor.permute(1, 0) # Batch x Seq => Seq x Batch + return padded_tensor + + def _tokenize(self, text): + # Simple tokenizer: strip out all non-alphabetic characters. + text = re.sub("[^a-zA-Z0-9\- ]", " ", text) + word_ids = list(map(self._get_word_id, text.split())) + return word_ids + + def _get_word_id(self, word): + if word not in self.word2id: + if len(self.word2id) >= self.MAX_VOCAB_SIZE: + return self.word2id[""] + + self.id2word.append(word) + self.word2id[word] = len(self.word2id) + + return self.word2id[word] + + def _discount_rewards(self, last_values): + returns, advantages = [], [] + R = last_values.data + for t in reversed(range(len(self.transitions))): + rewards, _, _, values = self.transitions[t] + R = rewards + self.GAMMA * R + adv = R - values + returns.append(R) + advantages.append(adv) + + return returns[::-1], advantages[::-1] + + +def play(agent, path, max_step=50, nb_episodes=10, verbose=True): + """ + This code uses the cooking agent design in the spaceship game. + + :param agent: the obj of NeuralAgent, a sample object for the agent + :param path: The path to the game (envo model) + """ + infos_to_request = agent.infos_to_request + infos_to_request.max_score = True # Needed to normalize the scores. + + gamefiles = [path] + if os.path.isdir(path): + gamefiles = glob(os.path.join(path, "*.ulx")) + + env_id = textworld.gym.register_games(gamefiles, + request_infos=infos_to_request, + max_episode_steps=max_step) + env = gym.make(env_id) # Create a Gym environment to play the text game. + + if verbose: + if os.path.isdir(path): + print(os.path.dirname(path), end="") + else: + print(os.path.basename(path), end="") + + # Collect some statistics: nb_steps, final reward. + avg_moves, avg_scores, avg_norm_scores, seed_h = [], [], [], None + for no_episode in range(nb_episodes): + obs, infos = env.reset() # Start new episode. + seed_h = env.env.textworld_env._wrapped_env.seed(init_seed=seed_h) + + score = 0 + done = False + nb_moves = 0 + while not done: + command = agent.act(obs, score, done, infos) + obs, score, done, infos = env.step(command) + nb_moves += 1 + + agent.act(obs, score, done, infos) # Let the agent know the game is done. + + if verbose: + print(".", end="") + avg_moves.append(nb_moves) + avg_scores.append(score) + avg_norm_scores.append(score / infos["max_score"]) + + env.close() + msg = " \tavg. steps: {:5.1f}; avg. score: {:4.1f} / {}." + if verbose: + if os.path.isdir(path): + print(msg.format(np.mean(avg_moves), np.mean(avg_norm_scores), 1)) + else: + print(msg.format(np.mean(avg_moves), np.mean(avg_scores), infos["max_score"])) + + +agent = NeuralAgent() + +print("Training") +agent.train() # Tell the agent it should update its parameters. + +starttime = time() +play(agent, "./games/levelMedium.ulx", nb_episodes=500, verbose=False) # Medium level game. +print("Trained in {:.2f} secs".format(time() - starttime)) + +print('============== Time To Test ============== ') + +print("Testing") +agent.test() +play(agent, "./games/levelMedium.ulx") # Medium level game. diff --git a/textworld/challenges/spaceship/maker.py b/textworld/challenges/spaceship/maker.py new file mode 100644 index 00000000..90d81bb6 --- /dev/null +++ b/textworld/challenges/spaceship/maker.py @@ -0,0 +1,1115 @@ +import os +from os.path import join as pjoin +from typing import Optional + +from textworld import GameMaker +from textworld.generator.data import KnowledgeBase +from textworld import g_rng +from textworld.helpers import start +from textworld.utils import make_temp_directory +import textworld +from textworld.generator.game import Event, Quest, GameOptions +from textworld.generator import World + + +g_rng.set_seed(20190826) +PATH = pjoin(os.path.dirname(__file__), 'textworld_data') + + +def spaceship_maker_level_easy(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='Spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + + sleep_bag = gm.new(type='c', name="sleeping bag") # Provide the type and the name of the object. + sleep_bag.infos.desc = "cool! You can sleep in a comfy bag." # Text to display when issuing command "examine note". + sleep_station.add(sleep_bag) # Sleeping bag is fixed in place in the Sleep Station. + gm.add_fact("open", sleep_bag) + + card_box = gm.new(type='c') # Card box is a container which is fixed in place in the Sleep Station. + card_box.infos.desc = "It is empty." + sleep_station.add(card_box) # The card box contains nothing at this game + gm.add_fact("closed", card_box) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + key = gm.new(type='k', name="electronic key") + key.infos.desc = "This key opens the door into the modules area. In this space craft, the gravity is not a " \ + "challenge. Thus, you can find things on the floor." + us_lab.add(key) # When added directly to a room, portable objects are put on the floor. + + corridor1 = gm.connect(sleep_station.south, us_lab.north) + doorA = gm.new_door(corridor1, name="door A") + gm.add_fact("closed", doorA) # Add a fact about the door, e.g. here it is closed. + + # ===== Russian Module Design ====================================================================================== + russian_module = gm.new_room("Russian Module") + supporter = gm.new(type='s') # When not provided, names are automatically generated. + russian_module.add(supporter) # Supporters are fixed in place. + key_code = gm.new(type='k', name="digital key") + key_code.infos.desc = "This key is in fact a digital code which opens the secured box in the control modules " \ + "area. The code, in fact, is written on the supporter." + supporter.add(key_code) + + corridor2 = gm.connect(us_lab.south, russian_module.north) + doorB = gm.new_door(corridor2, name="door B") + gm.add_fact("locked", doorB) + gm.add_fact("match", key, doorB) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Control Module Design ====================================================================================== + control_module = gm.new_room("Control Module") + secured_box = gm.new(type='c', name='Secured box') # When not provided, names are automatically generated. + secured_box.infos.desc = "This box is highly secured with a complex code that is in one of the modules in the " \ + "craft. To open the box, you should just find that code key." + gm.add_fact("locked", secured_box) + gm.add_fact("match", key_code, secured_box) + secured_box.infos.desc = "The Secret Codes Handbook is in this box." + control_module.add(secured_box) # Supporters are fixed in place. + book = gm.new(type='o', name='Secret Codes Handbook') # New portable object with a randomly generated name. + secured_box.add(book) + + corridor3 = gm.connect(russian_module.west, control_module.east) + doorC = gm.new_door(corridor3, name='door C') + gm.add_fact("open", doorC) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(sleep_station) + + pencil = gm.new(type='o', name='pencil') # New portable object with a randomly generated name. + gm.inventory.add(pencil) # Add the object to the player's inventory. + gm.render(interactive=True) + + quest = gm.new_quest_using_commands(['open door A', 'go south', 'take electronic key', + 'unlock door B with electronic key', 'open door B', 'go south', + 'take digital key from board', 'go west', + 'unlock Secured box with digital key', 'open Secured box', + 'take Secret Codes Handbook from Secured box']) + print(" > ".join(quest.commands)) + + gm.quests.append(quest) + + +def spaceship_maker_level_medium(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='Spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + sleep_bag = gm.new(type='c', name="sleeping bag") + sleep_bag.infos.desc = "cool! You can sleep in a comfy bag." + sleep_station.add(sleep_bag) # Sleeping bag is fixed in place in the Sleep Station. + gm.add_fact("open", sleep_bag) + + surf_1 = gm.new(type='s', name='vertical desk') # surf_1 is a table (supporter) in the Sleep Station. + surf_1.infos.desc = "This is not a regular table. The surface is installed vertically and your objects are " \ + "attached or hooked to it, why? Come on! we are in space, there is no gravity here." + sleep_station.add(surf_1) # The card box contains nothing at this game + + # laptop = gm.new(type='o', name="laptop") + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails. " + surf_1.add(laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + us_lab.infos.desc = "This is where Americans do their research on Space. In addition to all computers and " \ + "lab gadgets, you can find a couple of objects here which are useful during our game. Let's " \ + "explore the room." + + box_a = gm.new(type='c', name="box A") + box_a.infos.desc = "This a regular box, keeps the electronic key to open door C. But it is locked. The lock " \ + "looks like a keypad, means that the key is in fact just a code! So, ... let's search around " \ + "to find its key." + us_lab.add(box_a) + gm.add_fact("locked", box_a) + + key_1 = gm.new(type='k', name="electronic key 1") + key_1.infos.desc = "This key is a card key which opens door C." + box_a.add(key_1) + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + + # ===== European Module Design ===================================================================================== + european_module = gm.new_room("European Module") + european_module.infos.desc = "This room belongs to European scientists. Isn't it cool? what do they research? " \ + "well, we can explore it later... For now, there is a key code here. This code " \ + "opens the box in the next room and consequently takes you to the next stage. So, " \ + "explore the table to find the key." + + surf_2 = gm.new(type='s', name='table') + surf_2.infos.desc = "This is a simple table located in the middle of the room. Let's take a look at it..." + european_module.add(surf_2) + + box_b = gm.new(type='c', name="box B") + box_b.infos.desc = "This a regular box, keeps the key to open box A." + surf_2.add(box_b) + gm.add_fact("closed", box_b) + + key_2 = gm.new(type='k', name="code key 1") + key_2.infos.desc = "This key is in fact a digital code which opens the box in the US Lab area. The code, " \ + "in fact, is written on a piece of paper." + box_b.add(key_2) + gm.add_fact("match", key_2, box_a) + + chair_1 = gm.new(type='s', name='chair') + chair_1.infos.desc = "this is a dark-gray chair which is developed to be used in space." + european_module.add(chair_1) + + corridor2 = gm.connect(us_lab.east, european_module.west) + + # ===== Russian Module Design ====================================================================================== + russian_module = gm.new_room("Russian Module") + russian_module.infos.desc = "The Russian module is a typical space lab that you can expect, filled with a " \ + "lot of processing machines, test equipments and space drive cars, in fact for " \ + "repair and test. Since it is located at the center of International Space Station, " \ + "it is also important room for everyone. There are many other objects here and " \ + "there belongs to other astronauts, probably that's why here looks a bit messy. " \ + "There are some stuffs here you should pick, obviously if you can find them among " \ + "all this mess." + + surf_3 = gm.new(type='s', name='metal table') + surf_3.infos.desc = "This is a big metal table, a messy one, there are many things on it, it is difficult to " \ + "find what you want. However, there is just one item which is important for you. Try to " \ + "find that item." + russian_module.add(surf_3) + + papers = gm.new(type='o', name='bunch of sticked papers') + surf_3.add(papers) + + notebooks = gm.new(type='o', name='lots of hanged notebooks') + surf_3.add(notebooks) + + tools = gm.new(type='o', name='attached bags for mechanical tools') + surf_3.add(tools) + + box_c = gm.new(type='c', name="box C") + box_c.infos.desc = "This box is locked! sounds it carries important item... So, let's find its key to open it. " \ + "Wait... strange! the lock looks like a heart!! Wait we've seen something similar to this " \ + "somewhere before." + surf_3.add(box_c) + gm.add_fact("locked", box_c) + + key_3 = gm.new(type='k', name="digital key 1") + key_3.infos.desc = "This key is an important key in this craft. If you want to leave the spaceship, you " \ + "definitely need this key." + box_c.add(key_3) + + surf_4 = gm.new(type='s', name='wall-mounted surface') + surf_4.infos.desc = "This is a wall-mounted surface which different instruments are installed on this. These " \ + "instruments are basically control various modules and doors in the shuttle." + russian_module.add(surf_4) + + box_d = gm.new(type='c', name="exit box") + box_d.infos.desc = "The most important box here, which is in fact locked! sounds it carries important item... " \ + "So, let's find its key to open it." + surf_4.add(box_d) + gm.add_fact("locked", box_d) + + push_button = gm.new(type='b', name="exit push button") + push_button.infos.desc = "This push button is a key-like object which opens door A." + gm.add_fact("unpushed", push_button) + box_d.add(push_button) + + corridor3 = gm.connect(us_lab.south, russian_module.north) + door_b = gm.new_door(corridor3, name="door B") + gm.add_fact("locked", door_b) + gm.add_fact("match", key_1, door_b) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Lounge Design ============================================================================================== + lounge = gm.new_room("Lounge Module") + lounge.infos.desc = "This lounge is very quiet room with a big round window to the space. Wow, you can look " \ + "to our beloved Earth from this window. This room is the place that you can stay here for " \ + "hours and just get relax. This room also contains some other stuff, let's explore what " \ + "they are ..." + + box_e = gm.new(type='c', name="box E") + box_e.infos.desc = "This box is actually a wall-mounted bag and you can put an object into it. Since we have no " \ + "gravity in the space, you can't just simply leave the object in the room. The object should " \ + "be hooked or inserted into a container like this bag. Well, know we know what it is!" + lounge.add(box_e) + gm.add_fact("closed", box_e) + + key_4 = gm.new(type='k', name="electronic key 2") + key_4.infos.desc = "This key is the key opens the door to the control room. Although it looks like a regular " \ + "iron key, it is very special metal key! Not any other key can be like it. Make sure to keep " \ + "it in safe place." + box_e.add(key_4) + + corridor4 = gm.connect(russian_module.east, lounge.west) + + # ===== Control Module Design ====================================================================================== + control_module = gm.new_room("Control Module") + control_module.infos.desc = "This is the heart of this spaceship! Wow ... look around, all the monitors and " \ + "panels. It is like you can control everything from here; more interestingly, you " \ + "can communicate with people on the Earth. There are also super important objects " \ + "kept in this room. Let's find them." + + box_f = gm.new(type='c', name="secured box") + box_f.infos.desc = "This box is secured very much, simple box with a complex, strange keypad to enter the code! " \ + "So ... it should contain extremely important items in it. Isn't it the thing you are " \ + "looking for?!" + control_module.add(box_f) + gm.add_fact("locked", box_f) + gm.add_fact("match", key_3, box_f) + + book = gm.new(type='o', name='Secret Codes Handbook') + book.infos.desc = "If you open and check this book, here it is the description: 'This is a book of all secret " \ + "codes to manage different actions and functions inside the International Space Station. " \ + "These codes are pre-authorized by the main control room at Earth unless it is mentioned.'" \ + " " \ + "On the second page of the book, you can find this: 'To open the hatch door you should have " \ + "both two keys in the secured box. ATTENTION: you MUST have the outfit on you, before opening " \ + "the hatch. Otherwise, your life is in fatal danger.'" + box_f.add(book) + + key_5 = gm.new(type='k', name="digital key 2") + box_f.add(key_5) + gm.add_fact("match", key_5, box_d) + key_6 = gm.new(type='k', name="code key 2") + box_f.add(key_6) + + corridor5 = gm.connect(control_module.east, russian_module.west) + door_c = gm.new_door(corridor5, name="door C") + gm.add_fact("locked", door_c) + gm.add_fact("match", key_4, door_c) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Hatch Design =============================================================================================== + hatch = gm.new_room("Hatch") + hatch.infos.desc = "This area is like the entrance to the spaceship, so like home entrance with outer and " \ + "inner doors and a place that outfits are hooked. There are only two important differences: " \ + "first, if the outer door is open and you don't have outfit on you, you are dead!! No joke " \ + "here! So make sure that you open the door after wearing those cloths. Second, the door nob " \ + "to open the door is not neither on the door nor in this room. You should open the external " \ + "door from Russian Module! woooh so much of safety concerns, yeah?!" + + cloth = gm.new(type='l', name="outfit") + hatch.add(cloth) + + corridor6 = gm.connect(hatch.north, lounge.south) + door_d = gm.new_door(corridor6, name="door D") + gm.add_fact("locked", door_d) + gm.add_fact("match", key_6, door_d) + + # ===== Outside Spaceship (Space) Design =========================================================================== + outside = gm.new_room("Outside") + outside.infos.desc = "Here is outside the spaceship. No Oxygen, no gravity, nothing! If you are here, it means " \ + "that you have the special outfit on you and you passed the medium level of the game! " \ + "Congrats!" + + corridor7 = gm.connect(outside.north, hatch.south) + door_e = gm.new_door(corridor7, name="door E") + gm.add_fact("locked", door_e) + gm.add_fact("pair", push_button, door_e) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(sleep_station) + + key_7 = gm.new(type='k', name="hearty key") + key_7.infos.desc = "This key is shaped like a heart, not a normal key for a spaceship, ha ha ha..." + gm.add_fact("match", key_7, box_c) + gm.inventory.add(key_7) # Add the object to the player's inventory. + + # gm.render(interactive=True) + + # quest = gm.new_quest_using_commands(['examine laptop', + # 'open door A', + # 'go south', + # 'go east', + # 'open box B', + # 'take code key 1 from box B', + # 'go west', + # 'unlock box A with code key 1', + # 'open box A', + # 'take electronic key 1 from box A', + # 'unlock door B with electronic key 1', + # 'open door B', + # 'go south', + # 'examine box C', + # 'unlock box C with hearty key', + # 'open box C', + # 'take digital key 1 from box C', + # 'go east', + # 'open box E', + # 'take electronic key 2 from box E', + # 'go west', + # 'unlock door C with electronic key 2', + # 'open door C', + # 'go west', + # 'unlock secured box with digital key 1', + # 'open secured box', + # 'take Secret Codes Handbook from secured box', + # 'examine Secret Codes Handbook', + # 'take code key 2 from secured box', + # 'take digital key 2 from secured box', + # 'go east', + # 'go east', + # 'unlock door D with code key 2', + # 'open door D', + # 'go south', + # 'take outfit', + # 'wear the outfit', + # 'go north', + # 'go west', + # 'unlock exit box with digital key 2', + # 'open exit box', + # 'push exit push button', + # 'go east', + # 'go south', + # 'go south']) + + arr = ['examine laptop', + 'check email', + # 'open door A', + # 'go south', + # 'go east', + # 'open box B', + # 'take code key 1 from box B', + # 'go west', + # 'unlock box A with code key 1', + # 'open box A', + # 'take electronic key 1 from box A', + # 'unlock door B with electronic key 1', + # 'open door B', + # 'go south', + # 'examine box C', + # 'unlock box C with hearty key', + # 'open box C', + # 'take digital key 1 from box C', + # 'go east', + # 'open box E', + # 'take electronic key 2 from box E', + # 'go west', + # 'unlock door C with electronic key 2', + # 'open door C', + # 'go west', + # 'unlock secured box with digital key 1', + # 'open secured box', + # 'take Secret Codes Handbook from secured box', + # 'examine Secret Codes Handbook', + # 'take code key 2 from secured box', + # 'take digital key 2 from secured box', + # 'go east', + # 'go east', + # 'unlock door D with code key 2', + # 'open door D', + # 'go south', + # 'take outfit', + # 'wear the outfit', + # 'go north', + # 'go west', + # 'unlock exit box with digital key 2', + # 'open exit box', + # 'push exit push button', + # 'go east', + # 'go south', + # 'go south' + ] + test_commands(gm, arr) + + +def test(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='Spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + surf_1 = gm.new(type='s', name='wall-mounted surface') + surf_1.infos.desc = "This is a wall-mounted surface which different instruments are installed on this. These " \ + "instruments are basically control various modules and doors in the shuttle." + sleep_station.add(surf_1) + + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails. " + surf_1.add(laptop) + gm.add_fact("turned_off", laptop) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(sleep_station) + + gm.render(interactive=True) + + gm.record_quest() + + +def test_commands(game, arr): + with make_temp_directory() as tmpdir: + silent = False + game_file = game.compile(pjoin(tmpdir, "test_game_1.ulx")) + env = start(game_file) + env.activate_state_tracking() + env.reset() + + agent = textworld.agents.HumanAgent(autocompletion=True) + agent.reset(env) + + if not silent: + env.render(mode="human") + + try: + for command in arr: + print(command) + game_state, reward, done = env.step(command) + + if not silent: + env.render() + + print("Available actions: {}\n".format(game_state.admissible_commands)) + print('==================================================') + + except KeyboardInterrupt: + pass # Stop the game. + finally: + env.close() + + +def spaceship_maker_level_medium_v1(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + sleep_bag = gm.new(type='c', name="sleeping bag") + sleep_bag.infos.desc = "cool! You can sleep in a comfy bag." + sleep_station.add(sleep_bag) # Sleeping bag is fixed in place in the Sleep Station. + gm.add_fact("open", sleep_bag) + + surf_1 = gm.new(type='s', name='vertical desk') # surf_1 is a table (supporter) in the Sleep Station. + surf_1.infos.desc = "This is not a regular table. The surface is installed vertically and your objects are " \ + "attached or hooked to it, why? Come on! we are in space, there is no gravity here." + sleep_station.add(surf_1) # The card box contains nothing at this game + + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails." + surf_1.add(laptop) + gm.add_fact('unread/e', laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + us_lab.infos.desc = "This is where Americans do their research on Space. In addition to all computers and " \ + "lab gadgets, you can find a couple of objects here which are useful during our game. Let's " \ + "explore the room." + + box_a = gm.new(type='c', name="box A") + box_a.infos.desc = "This a regular box, keeps the electronic key to open door C. But it is locked. The lock " \ + "looks like a keypad, means that the key is in fact just a code! So, ... let's search around " \ + "to find its key." + us_lab.add(box_a) + gm.add_fact("locked", box_a) + + key_1 = gm.new(type='k', name="electronic key 1") + key_1.infos.desc = "This key is a card key which opens door C." + box_a.add(key_1) + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + + # ===== European Module Design ===================================================================================== + european_module = gm.new_room("European Module") + european_module.infos.desc = "This room belongs to European scientists. Isn't it cool? what do they research? " \ + "well, we can explore it later... For now, there is a key code here. This code " \ + "opens the box in the next room and consequently takes you to the next stage. So, " \ + "explore the table to find the key." + + surf_2 = gm.new(type='s', name='table') + surf_2.infos.desc = "This is a simple table located in the middle of the room. Let's take a look at it..." + european_module.add(surf_2) + + box_b = gm.new(type='c', name="box B") + box_b.infos.desc = "This a regular box, keeps the key to open box A." + surf_2.add(box_b) + gm.add_fact("closed", box_b) + + key_2 = gm.new(type='k', name="code key 1") + key_2.infos.desc = "This key is in fact a digital code which opens the box in the US Lab area. The code, " \ + "in fact, is written on a piece of paper." + box_b.add(key_2) + gm.add_fact("match", key_2, box_a) + + chair_1 = gm.new(type='s', name='chair') + chair_1.infos.desc = "this is a dark-gray chair which is developed to be used in space." + european_module.add(chair_1) + + corridor2 = gm.connect(us_lab.east, european_module.west) + + # ===== Russian Module Design ====================================================================================== + russian_module = gm.new_room("Russian Module") + russian_module.infos.desc = "The Russian module is a typical space lab that you can expect, filled with a " \ + "lot of processing machines, test equipments and space drive cars, in fact for " \ + "repair and test. Since it is located at the center of International Space Station, " \ + "it is also important room for everyone. There are many other objects here and " \ + "there belongs to other astronauts, probably that's why here looks a bit messy. " \ + "There are some stuffs here you should pick, obviously if you can find them among " \ + "all this mess." + + surf_3 = gm.new(type='s', name='metal table') + surf_3.infos.desc = "This is a big metal table, a messy one, there are many things on it, it is difficult to " \ + "find what you want. However, there is just one item which is important for you. Try to " \ + "find that item." + russian_module.add(surf_3) + + papers = gm.new(type='o', name='bunch of sticked papers') + surf_3.add(papers) + + notebooks = gm.new(type='o', name='lots of hanged notebooks') + surf_3.add(notebooks) + + tools = gm.new(type='o', name='attached bags for mechanical tools') + surf_3.add(tools) + + box_c = gm.new(type='c', name="box C") + box_c.infos.desc = "This box is locked! sounds it carries important item... So, let's find its key to open it. " \ + "Wait... strange! the lock looks like a heart!! Wait we've seen something similar to this " \ + "somewhere before." + surf_3.add(box_c) + gm.add_fact("locked", box_c) + + key_3 = gm.new(type='k', name="digital key 1") + key_3.infos.desc = "This key is an important key in this craft. If you want to leave the spaceship, you " \ + "definitely need this key." + box_c.add(key_3) + + surf_4 = gm.new(type='s', name='wall-mounted surface') + surf_4.infos.desc = "This is a wall-mounted surface which different instruments are installed on this. These " \ + "instruments are basically control various modules and doors in the shuttle." + russian_module.add(surf_4) + + box_d = gm.new(type='c', name="exit box") + box_d.infos.desc = "The most important box here, which is in fact locked! sounds it carries important item... " \ + "So, let's find its key to open it." + surf_4.add(box_d) + gm.add_fact("locked", box_d) + + push_button = gm.new(type='b', name="exit push button") + push_button.infos.desc = "This push button is a key-like object which opens door A." + gm.add_fact("unpushed", push_button) + box_d.add(push_button) + + corridor3 = gm.connect(us_lab.south, russian_module.north) + door_b = gm.new_door(corridor3, name="door B") + gm.add_fact("locked", door_b) + gm.add_fact("match", key_1, door_b) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Lounge Design ============================================================================================== + lounge = gm.new_room("Lounge Module") + lounge.infos.desc = "This lounge is very quiet room with a big round window to the space. Wow, you can look " \ + "to our beloved Earth from this window. This room is the place that you can stay here for " \ + "hours and just get relax. This room also contains some other stuff, let's explore what " \ + "they are ..." + + box_e = gm.new(type='c', name="box E") + box_e.infos.desc = "This box is actually a wall-mounted bag and you can put an object into it. Since we have no " \ + "gravity in the space, you can't just simply leave the object in the room. The object should " \ + "be hooked or inserted into a container like this bag. Well, know we know what it is!" + lounge.add(box_e) + gm.add_fact("closed", box_e) + + key_4 = gm.new(type='k', name="electronic key 2") + key_4.infos.desc = "This key is the key opens the door to the control room. Although it looks like a regular " \ + "iron key, it is very special metal key! Not any other key can be like it. Make sure to keep " \ + "it in safe place." + box_e.add(key_4) + + corridor4 = gm.connect(russian_module.east, lounge.west) + + # ===== Control Module Design ====================================================================================== + control_module = gm.new_room("Control Module") + control_module.infos.desc = "This is the heart of this spaceship! Wow ... look around, all the monitors and " \ + "panels. It is like you can control everything from here; more interestingly, you " \ + "can communicate with people on the Earth. There are also super important objects " \ + "kept in this room. Let's find them." + + box_f = gm.new(type='c', name="secured box") + box_f.infos.desc = "This box is secured very much, simple box with a complex, strange keypad to enter the code! " \ + "So ... it should contain extremely important items in it. Isn't it the thing you are " \ + "looking for?!" + control_module.add(box_f) + gm.add_fact("locked", box_f) + gm.add_fact("match", key_3, box_f) + + book = gm.new(type='txt', name='Secret Codes Handbook') + book.infos.desc = "If you open and check this book, here it is the description: 'This is a book of all secret " \ + "codes to manage different actions and functions inside the International Space Station. " \ + "These codes are pre-authorized by the main control room at Earth unless it is mentioned.'" \ + " " \ + "On the second page of the book, you can find this: 'To open the hatch door you should have " \ + "both two keys in the secured box. ATTENTION: you MUST have the outfit on you, before opening " \ + "the hatch. Otherwise, your life is in fatal danger.'" + box_f.add(book) + gm.add_fact("unread/t", book) + + key_5 = gm.new(type='k', name="digital key 2") + box_f.add(key_5) + gm.add_fact("match", key_5, box_d) + key_6 = gm.new(type='k', name="code key 2") + box_f.add(key_6) + + corridor5 = gm.connect(control_module.east, russian_module.west) + door_c = gm.new_door(corridor5, name="door C") + gm.add_fact("locked", door_c) + gm.add_fact("match", key_4, door_c) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Hatch Design =============================================================================================== + hatch = gm.new_room("Hatch") + hatch.infos.desc = "This area is like the entrance to the spaceship, so like home entrance with outer and " \ + "inner doors and a place that outfits are hooked. There are only two important differences: " \ + "first, if the outer door is open and you don't have outfit on you, you are dead!! No joke " \ + "here! So make sure that you open the door after wearing those cloths. Second, the door nob " \ + "to open the door is not neither on the door nor in this room. You should open the external " \ + "door from Russian Module! woooh so much of safety concerns, yeah?!" + + cloth = gm.new(type='l', name="outfit") + hatch.add(cloth) + gm.add_fact("takenoff", cloth) + gm.add_fact("clean", cloth) + + corridor6 = gm.connect(hatch.north, lounge.south) + door_d = gm.new_door(corridor6, name="door D") + gm.add_fact("locked", door_d) + gm.add_fact("match", key_6, door_d) + + # ===== Outside Spaceship (Space) Design =========================================================================== + outside = gm.new_room("Outside") + outside.infos.desc = "Here is outside the spaceship. No Oxygen, no gravity, nothing! If you are here, it means " \ + "that you have the special outfit on you and you passed the medium level of the game! " \ + "Congrats!" + + corridor7 = gm.connect(outside.north, hatch.south) + door_e = gm.new_door(corridor7, name="door E") + gm.add_fact("locked", door_e) + gm.add_fact("pair", push_button, door_e) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(sleep_station) + # gm.set_player(us_lab) + + key_7 = gm.new(type='k', name="hearty key") + key_7.infos.desc = "This key is shaped like a heart, not a normal key for a spaceship, ha ha ha..." + gm.add_fact("match", key_7, box_c) + gm.inventory.add(key_7) # Add the object to the player's inventory. + + # gm.render(interactive=True) + + # gm.grammar = textworld.generator.make_grammar() + + + # array_of_all_required_actions_to_win = ['examine laptop', + # 'check email', + # 'open door A', + # 'go south', + # 'go east', + # 'open box B', + # 'take code key 1 from box B', + # 'go west', + # 'unlock box A with code key 1', + # 'open box A', + # 'take electronic key 1 from box A', + # 'unlock door B with electronic key 1', + # 'open door B', + # 'go south', + # 'examine box C', + # 'unlock box C with hearty key', + # 'open box C', + # 'take digital key 1 from box C', + # 'go east', + # 'open box E', + # 'take electronic key 2 from box E', + # 'go west', + # 'unlock door C with electronic key 2', + # 'open door C', + # 'go west', + # 'unlock secured box with digital key 1', + # 'open secured box', + # 'take Secret Codes Handbook from secured box', + # 'read Secret Codes Handbook', + # 'take code key 2 from secured box', + # 'take digital key 2 from secured box', + # 'go east', + # 'go east', + # 'unlock door D with code key 2', + # 'open door D', + # 'go south', + # 'take outfit', + # 'wear the outfit', + # 'go north', + # 'go west', + # 'unlock exit box with digital key 2', + # 'open exit box', + # 'push exit push button', + # 'go east', + # 'go south', + # 'go south'] + # + # array_of_actions_for_a_fail_example1 = ['examine laptop', + # 'check email', + # 'open door A', + # 'go south', + # 'go east', + # 'open box B', + # 'take code key 1 from box B', + # 'go west', + # 'unlock box A with code key 1', + # 'open box A', + # 'take electronic key 1 from box A', + # 'unlock door B with electronic key 1', + # 'open door B', + # 'go south', + # 'examine box C', + # 'unlock box C with hearty key', + # 'open box C', + # 'take digital key 1 from box C', + # 'go east', + # 'open box E', + # 'take electronic key 2 from box E', + # 'go west', + # 'unlock door C with electronic key 2', + # 'open door C', + # 'go west', + # 'unlock secured box with digital key 1', + # 'open secured box', + # 'take digital key 2 from secured box', + # 'go east', + # 'unlock exit box with digital key 2', + # 'open exit box', + # 'push exit push button', + # 'go east', + # 'go south', + # 'go south'] + # + # array_of_actions_for_a_fail_example2 = ['examine laptop', + # 'check email', + # 'open door A'] + + quest_design(gm) + + # test_commands(gm, ['look', 'open door A', 'go south']) + test_commands(gm, ['open door A', 'go north', 'go south']) + # return quest_design(gm) + + +def spaceship_maker_level_medium_v2(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + sleep_bag = gm.new(type='c', name="sleeping bag") + sleep_bag.infos.desc = "cool! You can sleep in a comfy bag." + sleep_station.add(sleep_bag) # Sleeping bag is fixed in place in the Sleep Station. + gm.add_fact("open", sleep_bag) + + surf_1 = gm.new(type='s', name='vertical desk') # surf_1 is a table (supporter) in the Sleep Station. + surf_1.infos.desc = "This is not a regular table. The surface is installed vertically and your objects are " \ + "attached or hooked to it, why? Come on! we are in space, there is no gravity here." + sleep_station.add(surf_1) # The card box contains nothing at this game + + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails." + surf_1.add(laptop) + gm.add_fact('unread/e', laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + us_lab.infos.desc = "This is where Americans do their research on Space. In addition to all computers and " \ + "lab gadgets, you can find a couple of objects here which are useful during our game. Let's " \ + "explore the room." + + box_a = gm.new(type='c', name="box A") + box_a.infos.desc = "This a regular box, keeps the electronic key to open door C. But it is locked. The lock " \ + "looks like a keypad, means that the key is in fact just a code! So, ... let's search around " \ + "to find its key." + us_lab.add(box_a) + gm.add_fact("locked", box_a) + + key_1 = gm.new(type='k', name="electronic key 1") + key_1.infos.desc = "This key is a card key which opens door C." + box_a.add(key_1) + + cloth = gm.new(type='l', name="outfit") + us_lab.add(cloth) + gm.add_fact("takenoff", cloth) + gm.add_fact("clean", cloth) + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + + # # ===== European Module Design ===================================================================================== + # european_module = gm.new_room("European Module") + # european_module.infos.desc = "This room belongs to European scientists. Isn't it cool? what do they research? " \ + # "well, we can explore it later... For now, there is a key code here. This code " \ + # "opens the box in the next room and consequently takes you to the next stage. So, " \ + # "explore the table to find the key." + # + # surf_2 = gm.new(type='s', name='table') + # surf_2.infos.desc = "This is a simple table located in the middle of the room. Let's take a look at it..." + # european_module.add(surf_2) + # + # box_b = gm.new(type='c', name="box B") + # box_b.infos.desc = "This a regular box, keeps the key to open box A." + # surf_2.add(box_b) + # gm.add_fact("closed", box_b) + # + # key_2 = gm.new(type='k', name="code key 1") + # key_2.infos.desc = "This key is in fact a digital code which opens the box in the US Lab area. The code, " \ + # "in fact, is written on a piece of paper." + # box_b.add(key_2) + # gm.add_fact("match", key_2, box_a) + # + # chair_1 = gm.new(type='s', name='chair') + # chair_1.infos.desc = "this is a dark-gray chair which is developed to be used in space." + # european_module.add(chair_1) + # + # corridor_2 = gm.connect(us_lab.east, european_module.west) + # door_b = gm.new_door(corridor_2, name="door B") + # gm.add_fact("closed", door_b) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(us_lab) + + quest_design_2(gm) + + return gm + + +def quest_design_2(game): + quests = [] + + # 1. Is the Player performing successful in the Sleeping Station + win_quest = Event(conditions={ + game.new_fact("at", game._entities['P'], game._entities['r_0']) + }) + quests.append(Quest(win_events=[win_quest], fail_events=[], reward=0)) + + fail_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("unread/e", game._entities['cpu_0']), + }) + win_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("read/e", game._entities['cpu_0']), + }) + quests.append(Quest(win_events=[win_quest], fail_events=[fail_quest])) + + # 2. + win_quest = Event(conditions={game.new_fact("worn", game._entities['l_0'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + game.quests = quests + + return game.build() + + +def quest_design(game): + quests = [] + + # 1. Player is in the Sleeping Station + win_quest = Event(conditions={game.new_fact("read/e", game._named_entities['laptop'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + tp = Event(conditions={game.new_fact("unread/e", game._named_entities['laptop'])}) + fail_quest = tp + quests.append(Quest(win_events=[], fail_events=[fail_quest])) + + # 2. Player is in US LAB to find Electronic Key 1 + win_quest = Event(conditions={game.new_fact("in", game._named_entities['electronic key 1'], game._entities['I'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + # # 3. Player is in Russian Module and take digital Key 1 and/or push the button + # win_quest = Event(conditions={game.new_fact("in", game._named_entities['digital key 1'], game._entities['I'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # win_quest = Event(conditions={game.new_fact("pushed", game._named_entities['exit push button']), + # game.new_fact("worn", game._named_entities['outfit'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # fail_quest = Event(conditions={game.new_fact("pushed", game._named_entities['exit push button']), + # game.new_fact("takenoff", game._named_entities['outfit'])}) + # quests.append(Quest(win_events=[], fail_events=[fail_quest])) + # + # # 4. Player is the Control Module and take Electronic Key 2 + # win_quest = Event(conditions={game.new_fact("in", game._named_entities['digital key 2'], game._entities['I'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 5. Player reads the Secret Code book at Control Module + # win_quest = Event(conditions={game.new_fact("read/t", game._named_entities['Secret Codes Handbook'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 6. Player is in Hatch room and wears the cloth + # win_quest = Event(conditions={game.new_fact("worn", game._named_entities['outfit'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 7. Player goes outside + # win_quest = Event(conditions={game.new_fact("at", game._entities['P'], game._named_entities['Outside'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + + game.quests = quests + + _game = game.build() + + return _game + + +def testFW_easyGame(): + # GameMaker object for handcrafting text-based games. + kb = KnowledgeBase.load(target_dir=PATH) + gm = GameMaker(kb=kb, theme='Spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + + surf_1 = gm.new(type='s', name='vertical desk') # surf_1 is a table (supporter) in the Sleep Station. + sleep_station.add(surf_1) # The card box contains nothing at this game + laptop = gm.new(type='cpu', name='laptop') + surf_1.add(laptop) + # gm.add_fact('turned_off', laptop) + gm.add_fact('unread/e', laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + + # ===== European Module Design ===================================================================================== + # european_module = gm.new_room("European Module") + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + # corridor_2 = gm.connect(sleep_station.east, european_module.west) + # door_b = gm.new_door(corridor_2, name="door B") + # gm.add_fact("closed", door_b) + # gm.add_fact("closed", door_a, door_b) + + gm.set_player(sleep_station) + gm.render(interactive=True) + + quests = [] + + # # A. The EVENT solution + # # ------------------------------------------ + # # 1. Player is in the Sleeping Station + # win_quest = Event(conditions={gm.new_fact("read/e", gm._named_entities['laptop'])}) + # fail_quest = Event(conditions={gm.new_fact("unread/e", gm._named_entities['laptop']), + # gm.new_fact("open", gm._named_entities['door A'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[fail_quest])) + # gm.quests = quests + + # # B. The NEW_EVENT_USING_COMMANDS solution + # # ------------------------------------------ + # win_quest = gm.new_event_using_commands(['open door A']) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # gm.quests = quests + + # # C. The NEW_QUEST_USING_COMMANDS solution + # # ------------------------------------------ + # quest = gm.new_quest_using_commands(['open door A']) + # gm.quests = [quest] + + # B. The RECORD_QUEST solution + # ------------------------------------------ + a = gm.record_quest() + + gm.test() + + +def create_world(options: Optional[GameOptions]): + kb = KnowledgeBase.load(target_dir=PATH) + options = options or GameOptions() + options.grammar.theme = 'Spaceship' + options.kb = kb + options.seeds = g_rng.seed + + rngs = options.rngs + rng_map = rngs['map'] + rng_objects = rngs['objects'] + rng_grammar = rngs['grammar'] + rng_quest = rngs['quest'] + + door_states = ["open", "closed", "locked"] + + # Generate map. + map_ = textworld.generator.make_map(n_rooms=options.nb_rooms, rng=rng_map, possible_door_states=door_states) + world = World.from_map(map_) + + # Randomly place the player. + starting_room = None + if len(world.rooms) > 1: + starting_room = rng_map.choice(world.rooms) + + world.set_player_room(starting_room) + + +if __name__ == "__main__": + # spaceship_maker_level_easy() + # spaceship_maker_level_medium() + # test() + # testFW_easyGame() + # spaceship_maker_level_medium_v1() + game = spaceship_maker_level_medium_v2() + test_commands(game, [ + 'open door A', + 'go north', + 'check laptop for email', + 'check laptop for email', + 'go south', + 'take outfit', + 'wear the outfit', + ]) + diff --git a/textworld/challenges/spaceship/spaceship_game.py b/textworld/challenges/spaceship/spaceship_game.py new file mode 100644 index 00000000..ee9a7ac0 --- /dev/null +++ b/textworld/challenges/spaceship/spaceship_game.py @@ -0,0 +1,700 @@ +import argparse +import os + +from os.path import join as pjoin +from typing import Mapping, Optional + +import textworld + +from textworld import g_rng +from textworld import GameMaker +from textworld.challenges import register +from textworld.generator.data import KnowledgeBase +from textworld.generator.game import GameOptions, Event, Quest, GameProgression + + +g_rng.set_seed(20190826) +PATH = os.path.dirname(__file__) + + +def build_argparser(parser=None): + parser = parser or argparse.ArgumentParser() + + group = parser.add_argument_group('Spaceship game settings') + group.add_argument("--level", required=True, choices=["easy", "medium", "difficult"], + help="The difficulty level. Must be between: easy, medium, or difficult.") + general_group = argparse.ArgumentParser(add_help=False) + general_group.add_argument("--third-party", metavar="PATH", + help="Load third-party module. Useful to register new custom challenges on-the-fly.") + return parser + + +def make_game_medium(settings: Mapping[str, str], options: Optional[GameOptions] = None) -> textworld.Game: + """ Make a Spaceship game of the desired difficulty settings. + + Arguments: + settings: Difficulty settings (see notes). + options: + For customizing the game generation (see + :py:class:`textworld.GameOptions ` + for the list of available options). + + Returns: + Generated game. + + Notes: + Difficulty levels are defined as follows: + * Level easy. + * Level medium. + * Level difficult. + + """ + kb = KnowledgeBase.load(target_dir=pjoin(os.path.dirname(__file__), 'textworld_data')) + options = options or GameOptions() + options.grammar.theme = 'spaceship' + options.kb = kb + options.seeds = g_rng.seed + + rngs = options.rngs + rng_map = rngs['map'] + rng_objects = rngs['objects'] + rng_grammar = rngs['grammar'] + rng_quest = rngs['quest'] + + if settings["level"] == 'easy': + mode = "easy" + options.nb_rooms = 4 + + elif settings["level"] == 'medium': + mode = "medium" + options.nb_rooms = 8 + + elif settings["level"] == 'difficult': + mode = "difficult" + options.nb_rooms = 8 + + metadata = {"desc": "Spaceship", # Collect information for reproduction. + "mode": mode, + "seeds": options.seeds, + "world_size": options.nb_rooms} + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Create the Game Environment + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + gm = GameMaker(options=options) + # gm = GameMaker(kb=kb, theme='spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + surf_0 = gm.new(type='s', name='vertical desk') # surf_0 is a table (supporter) in the Sleep Station. + surf_0.infos.desc = "This is not a regular table. The surface is installed vertically and your objects are " \ + "attached or hooked to it, why? Come on! we are in space, there is no gravity here." + sleep_station.add(surf_0) + + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails." + surf_0.add(laptop) + gm.add_fact('unread/e', laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + us_lab.infos.desc = "This is where Americans do their research on Space. In addition to all computers and " \ + "lab gadgets, you can find a couple of objects here which are useful during your mission. " \ + "Let's explore the room." + + box_a = gm.new(type='c', name="box A") + box_a.infos.desc = "This a regular box, keeps the electronic key to open box B. " + us_lab.add(box_a) + gm.add_fact("closed", box_a) + + key_0 = gm.new(type='k', name="electronic key") + key_0.infos.desc = "This key is an electronic key which unlocks box B. An electronic key is in fact a code and " \ + "opens those locks which are equipped with a keypad." + box_a.add(key_0) + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + + # ===== Russian Module Design ====================================================================================== + russian_module = gm.new_room("Russian Module") + russian_module.infos.desc = "The Russian module is a typical space lab that you can expect, filled with a " \ + "lot of processing machines, test equipments and space drive cars, in fact for " \ + "repair and test. Since it is located at the center of International Space Station, " \ + "it is also important room for everyone. There are many other objects here and " \ + "there belongs to other astronauts, probably that's why here looks a bit messy. " \ + "There are some stuffs here you should pick, obviously if you can find them among " \ + "all this mess." + + surf_1 = gm.new(type='s', name='metal table') + surf_1.infos.desc = "This is a big metal table, a messy one, there are many things on it, it is difficult to " \ + "find what you want. However, there is just one item which is important for you. Try to " \ + "find that item." + russian_module.add(surf_1) + + box_b = gm.new(type='c', name="box B") + box_b.infos.desc = "This box is locked! sounds it carries important item... So, let's find its key to open it. " \ + "Wait... strange! the lock looks like a keypad!! Wait we've seen something similar to this " \ + "somewhere before." + surf_1.add(box_b) + gm.add_fact("locked", box_b) + gm.add_fact("match", key_0, box_b) + + push_button = gm.new(type='b', name="exit push button") + push_button.infos.desc = "This push button is a key-like object which opens door C." + gm.add_fact("unpushed", push_button) + box_b.add(push_button) + + corridor_2 = gm.connect(us_lab.south, russian_module.north) + door_b = gm.new_door(corridor_2, name="door B") + gm.add_fact("closed", door_b) + + # ===== Hatch Design =============================================================================================== + hatch = gm.new_room("Hatch") + hatch.infos.desc = "This area is like the entrance to the spaceship, so like home entrance with outer and " \ + "inner doors and a place that outfits are hooked. There are only two important differences: " \ + "first, if the outer door is open and you don't have outfit on you, you are dead!! No joke " \ + "here! So make sure that you open the door after wearing those cloths. Second, the door nob " \ + "to open the door is not neither on the door nor in this room. You should open the external " \ + "door from Russian Module! woooh so much of safety concerns, yeah?!" + + cloth = gm.new(type='l', name="outfit") + hatch.add(cloth) + gm.add_fact("takenoff", cloth) + gm.add_fact("clean", cloth) + + corridor_3 = gm.connect(hatch.west, russian_module.east) + door_c = gm.new_door(corridor_3, name="door C") + gm.add_fact("closed", door_c) + + # ===== Outside Spaceship (Space) Design =========================================================================== + outside = gm.new_room("Outside") + outside.infos.desc = "Here is outside the spaceship. No Oxygen, no gravity, nothing! If you are here, it means " \ + "that you have the special outfit on you and you passed the medium level of the game " \ + "successfully! Congrats!" + + corridor_4 = gm.connect(outside.north, hatch.south) + door_d = gm.new_door(corridor_4, name="door D") + gm.add_fact("locked", door_d) + gm.add_fact("pair", push_button, door_d) + + # ===== Player and Inventory Design ================================================================================ + gm.set_player(sleep_station) + + game = quest_design_medium(gm) + + # from textworld.challenges.spaceship import maker + # maker.test_commands(gm, [ + # 'check laptop for email', + # 'check laptop for email', + # 'open door A', + # 'go south', + # 'open box A', + # 'take electronic key from box A', + # 'open door B', + # 'go south', + # + # 'unlock box B with electronic key', + # 'open box B', + # 'push exit push button', + # + # 'open door C', + # 'go east', + # 'take outfit', + # 'wear outfit', + # 'go west', + # 'go east', + # 'go south', + # + # # 'check laptop for email', + # # 'check laptop for email', + # # 'open door A', + # # 'go south', + # # 'open box A', + # # 'take electronic key from box A', + # # 'open door B', + # # 'go south', + # # 'open door C', + # # 'go east', + # # 'take outfit', + # # 'wear outfit', + # # 'go west', + # # 'unlock box B with electronic key', + # # 'open box B', + # # 'push exit push button', + # # 'go east', + # # 'go south', + # ]) + + game.metadata = metadata + uuid = "tw-spaceship-{level}".format(level=str.title(settings["level"])) + game.metadata["uuid"] = uuid + + return game + + +def make_game_difficult(settings: Mapping[str, str], options: Optional[GameOptions] = None) -> textworld.Game: + """ Make a Spaceship game of the desired difficulty settings. + + Arguments: + settings: Difficulty settings (see notes). + options: + For customizing the game generation (see + :py:class:`textworld.GameOptions ` + for the list of available options). + + Returns: + Generated game. + + Notes: + Difficulty levels are defined as follows: + * Level easy. + * Level medium. + * Level difficult. + + """ + kb = KnowledgeBase.load(target_dir=pjoin(os.path.dirname(__file__), 'textworld_data')) + options = options or GameOptions() + options.grammar.theme = 'spaceship' + options.kb = kb + options.seeds = g_rng.seed + + rngs = options.rngs + rng_map = rngs['map'] + rng_objects = rngs['objects'] + rng_grammar = rngs['grammar'] + rng_quest = rngs['quest'] + + if settings["level"] == 'easy': + mode = "easy" + options.nb_rooms = 4 + + elif settings["level"] == 'medium': + mode = "medium" + options.nb_rooms = 8 + + elif settings["level"] == 'difficult': + mode = "difficult" + options.nb_rooms = 8 + + metadata = {"desc": "Spaceship", # Collect information for reproduction. + "mode": mode, + "seeds": options.seeds, + "world_size": options.nb_rooms} + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Create the Game Environment + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + gm = GameMaker(kb=kb, theme='spaceship') + + # ===== Sleep Station Design ======================================================================================= + sleep_station = gm.new_room("Sleep Station") + sleep_station.infos.desc = "This is a typical bedroom in spaceship; here, it is called sleep station. It is " \ + "small but comfortable to take a good rest after a day full of missions. However, " \ + "today your mission will start from here. Wait to be notified by a message. So, you " \ + "should find that message first." \ + " " \ + "BTW, don't forget that when the Hatch door is open, you should already have worn " \ + "your specially-designed outfit to be able to enter and stay at Hatch area; otherwise " \ + "you'll die! Yes! Living in space is tough." + + sleep_bag = gm.new(type='c', name="sleeping bag") + sleep_bag.infos.desc = "cool! You can sleep in a comfy bag." + sleep_station.add(sleep_bag) # Sleeping bag is fixed in place in the Sleep Station. + gm.add_fact("open", sleep_bag) + + surf_1 = gm.new(type='s', name='vertical desk') # surf_1 is a table (supporter) in the Sleep Station. + surf_1.infos.desc = "This is not a regular table. The surface is installed vertically and your objects are " \ + "attached or hooked to it, why? Come on! we are in space, there is no gravity here." + sleep_station.add(surf_1) # The card box contains nothing at this game + + laptop = gm.new(type='cpu', name='laptop') + laptop.infos.desc = "This is your personal laptop which is attached to the surface of the table. You can do " \ + "regular things with this, like check your emails, watch YouTube, Skype with family,etc." \ + "Since you are here, we recommend you to check your emails. New missions are posted through " \ + "emails." + surf_1.add(laptop) + gm.add_fact('unread/e', laptop) + + # ===== US LAB Design ============================================================================================== + us_lab = gm.new_room("US LAB") + us_lab.infos.desc = "This is where Americans do their research on Space. In addition to all computers and " \ + "lab gadgets, you can find a couple of objects here which are useful during our game. Let's " \ + "explore the room." + + box_a = gm.new(type='c', name="box A") + box_a.infos.desc = "This a regular box, keeps the electronic key to open door C. But it is locked. The lock " \ + "looks like a keypad, means that the key is in fact just a code! So, ... let's search around " \ + "to find its key." + us_lab.add(box_a) + gm.add_fact("locked", box_a) + + key_1 = gm.new(type='k', name="electronic key 1") + key_1.infos.desc = "This key is a card key which opens door C." + box_a.add(key_1) + + corridor_1 = gm.connect(sleep_station.south, us_lab.north) + door_a = gm.new_door(corridor_1, name="door A") + gm.add_fact("closed", door_a) + + # ===== European Module Design ===================================================================================== + european_module = gm.new_room("European Module") + european_module.infos.desc = "This room belongs to European scientists. Isn't it cool? what do they research? " \ + "well, we can explore it later... For now, there is a key code here. This code " \ + "opens the box in the next room and consequently takes you to the next stage. So, " \ + "explore the table to find the key." + + surf_2 = gm.new(type='s', name='table') + surf_2.infos.desc = "This is a simple table located in the middle of the room. Let's take a look at it..." + european_module.add(surf_2) + + box_b = gm.new(type='c', name="box B") + box_b.infos.desc = "This a regular box, keeps the key to open box A." + surf_2.add(box_b) + gm.add_fact("closed", box_b) + + key_2 = gm.new(type='k', name="code key 1") + key_2.infos.desc = "This key is in fact a digital code which opens the box in the US Lab area. The code, " \ + "in fact, is written on a piece of paper." + box_b.add(key_2) + gm.add_fact("match", key_2, box_a) + + chair_1 = gm.new(type='s', name='chair') + chair_1.infos.desc = "this is a dark-gray chair which is developed to be used in space." + european_module.add(chair_1) + + corridor2 = gm.connect(us_lab.east, european_module.west) + + # ===== Russian Module Design ====================================================================================== + russian_module = gm.new_room("Russian Module") + russian_module.infos.desc = "The Russian module is a typical space lab that you can expect, filled with a " \ + "lot of processing machines, test equipments and space drive cars, in fact for " \ + "repair and test. Since it is located at the center of International Space Station, " \ + "it is also important room for everyone. There are many other objects here and " \ + "there belongs to other astronauts, probably that's why here looks a bit messy. " \ + "There are some stuffs here you should pick, obviously if you can find them among " \ + "all this mess." + + surf_3 = gm.new(type='s', name='metal table') + surf_3.infos.desc = "This is a big metal table, a messy one, there are many things on it, it is difficult to " \ + "find what you want. However, there is just one item which is important for you. Try to " \ + "find that item." + russian_module.add(surf_3) + + papers = gm.new(type='o', name='bunch of sticked papers') + surf_3.add(papers) + + notebooks = gm.new(type='o', name='lots of hanged notebooks') + surf_3.add(notebooks) + + tools = gm.new(type='o', name='attached bags for mechanical tools') + surf_3.add(tools) + + box_c = gm.new(type='c', name="box C") + box_c.infos.desc = "This box is locked! sounds it carries important item... So, let's find its key to open it. " \ + "Wait... strange! the lock looks like a heart!! Wait we've seen something similar to this " \ + "somewhere before." + surf_3.add(box_c) + gm.add_fact("locked", box_c) + + key_3 = gm.new(type='k', name="digital key 1") + key_3.infos.desc = "This key is an important key in this craft. If you want to leave the spaceship, you " \ + "definitely need this key." + box_c.add(key_3) + + surf_4 = gm.new(type='s', name='wall-mounted surface') + surf_4.infos.desc = "This is a wall-mounted surface which different instruments are installed on this. These " \ + "instruments are basically control various modules and doors in the shuttle." + russian_module.add(surf_4) + + box_d = gm.new(type='c', name="exit box") + box_d.infos.desc = "The most important box here, which is in fact locked! sounds it carries important item... " \ + "So, let's find its key to open it." + surf_4.add(box_d) + gm.add_fact("locked", box_d) + + push_button = gm.new(type='b', name="exit push button") + push_button.infos.desc = "This push button is a key-like object which opens door A." + gm.add_fact("unpushed", push_button) + box_d.add(push_button) + + corridor3 = gm.connect(us_lab.south, russian_module.north) + door_b = gm.new_door(corridor3, name="door B") + gm.add_fact("match", key_1, door_b) # Tell the game 'Electronic key' is matching the 'door B''s lock + if settings["level"] == 'difficult': + gm.add_fact("closed", door_b) + else: + gm.add_fact("locked", door_b) + + # ===== Lounge Design ============================================================================================== + lounge = gm.new_room("Lounge Module") + lounge.infos.desc = "This lounge is very quiet room with a big round window to the space. Wow, you can look " \ + "to our beloved Earth from this window. This room is the place that you can stay here for " \ + "hours and just get relax. This room also contains some other stuff, let's explore what " \ + "they are ..." + + box_e = gm.new(type='c', name="box E") + box_e.infos.desc = "This box is actually a wall-mounted bag and you can put an object into it. Since we have no " \ + "gravity in the space, you can't just simply leave the object in the room. The object should " \ + "be hooked or inserted into a container like this bag. Well, know we know what it is!" + lounge.add(box_e) + gm.add_fact("closed", box_e) + + key_4 = gm.new(type='k', name="electronic key 2") + key_4.infos.desc = "This key is the key opens the door to the control room. Although it looks like a regular " \ + "iron key, it is very special metal key! Not any other key can be like it. Make sure to keep " \ + "it in safe place." + box_e.add(key_4) + + corridor4 = gm.connect(russian_module.east, lounge.west) + + # ===== Control Module Design ====================================================================================== + control_module = gm.new_room("Control Module") + control_module.infos.desc = "This is the heart of this spaceship! Wow ... look around, all the monitors and " \ + "panels. It is like you can control everything from here; more interestingly, you " \ + "can communicate with people on the Earth. There are also super important objects " \ + "kept in this room. Let's find them." + + box_f = gm.new(type='c', name="secured box") + box_f.infos.desc = "This box is secured very much, simple box with a complex, strange keypad to enter the code! " \ + "So ... it should contain extremely important items in it. Isn't it the thing you are " \ + "looking for?!" + control_module.add(box_f) + gm.add_fact("locked", box_f) + gm.add_fact("match", key_3, box_f) + + book = gm.new(type='txt', name='Secret Codes Handbook') + book.infos.desc = "If you open and check this book, here it is the description: 'This is a book of all secret " \ + "codes to manage different actions and functions inside the International Space Station. " \ + "These codes are pre-authorized by the main control room at Earth unless it is mentioned.'" \ + " " \ + "On the second page of the book, you can find this: 'To open the hatch door you should have " \ + "both two keys in the secured box. ATTENTION: you MUST have the outfit on you, before opening " \ + "the hatch. Otherwise, your life is in fatal danger.'" + box_f.add(book) + gm.add_fact("unread/t", book) + + key_5 = gm.new(type='k', name="digital key 2") + box_f.add(key_5) + gm.add_fact("match", key_5, box_d) + + key_6 = gm.new(type='k', name="code key 2") + box_f.add(key_6) + + corridor5 = gm.connect(control_module.east, russian_module.west) + door_c = gm.new_door(corridor5, name="door C") + gm.add_fact("locked", door_c) + gm.add_fact("match", key_4, door_c) # Tell the game 'Electronic key' is matching the 'door B''s lock + + # ===== Hatch Design =============================================================================================== + hatch = gm.new_room("Hatch") + hatch.infos.desc = "This area is like the entrance to the spaceship, so like home entrance with outer and " \ + "inner doors and a place that outfits are hooked. There are only two important differences: " \ + "first, if the outer door is open and you don't have outfit on you, you are dead!! No joke " \ + "here! So make sure that you open the door after wearing those cloths. Second, the door nob " \ + "to open the door is not neither on the door nor in this room. You should open the external " \ + "door from Russian Module! woooh so much of safety concerns, yeah?!" + + cloth = gm.new(type='l', name="outfit") + hatch.add(cloth) + gm.add_fact("takenoff", cloth) + gm.add_fact("clean", cloth) + + corridor6 = gm.connect(hatch.north, lounge.south) + door_d = gm.new_door(corridor6, name="door D") + gm.add_fact("match", key_6, door_d) + if settings["level"] == 'difficult': + gm.add_fact("closed", door_d) + else: + gm.add_fact("locked", door_d) + + # ===== Outside Spaceship (Space) Design =========================================================================== + outside = gm.new_room("Outside") + outside.infos.desc = "Here is outside the spaceship. No Oxygen, no gravity, nothing! If you are here, it means " \ + "that you have the special outfit on you and you passed the medium level of the game! " \ + "Congrats!" + + corridor7 = gm.connect(outside.north, hatch.south) + door_e = gm.new_door(corridor7, name="door E") + gm.add_fact("locked", door_e) + gm.add_fact("pair", push_button, door_e) + + # ===== Player and Inventory Design ================================================================================ + if settings["level"] == 'difficult': + # Randomly place the player in a subset of rooms. + # The player can be randomly start from any room but two of them: Control Module and Outside + available_rooms = [] + for rum in gm.rooms: + if (rum is not gm._named_entities['Outside']) and (rum is not gm._named_entities['Control Module']): + available_rooms.append(rum) + + starting_room = None + if len(available_rooms) > 1: + starting_room = rng_map.choice(available_rooms) + + gm.set_player(starting_room) + + else: + gm.set_player(sleep_station) + + # key_7 = gm.new(type='k', name="hearty key") + # key_7.infos.desc = "This key is shaped like a heart, not a normal key for a spaceship, ha ha ha..." + # gm.add_fact("match", key_7, box_c) + # gm.inventory.add(key_7) # Add the object to the player's inventory. + + if settings["level"] == 'easy': + game = quest_design_easy(gm) + + elif settings["level"] == 'medium': + game = quest_design_medium(gm) + + elif settings["level"] == 'difficult': + game = quest_design_difficult(gm) + + # from textworld.challenges.spaceship import maker + # maker.test_commands(gm, [ + # 'check laptop for email', + # # 'check laptop for email', + # 'open door A', + # 'go south', + # ]) + + game.metadata = metadata + uuid = "tw-spaceship-{level}".format(level=str.title(settings["level"])) + game.metadata["uuid"] = uuid + + return game + + +def quest_design_easy(game): + return None + + +def quest_design_medium(game): + quests = [] + + # 1. Is the Player performing successful in the Sleeping Station + win_quest = Event(conditions={ + game.new_fact("at", game._entities['P'], game._entities['r_0']) + }) + quests.append(Quest(win_events=[win_quest], fail_events=[], reward=0)) + + fail_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("unread/e", game._entities['cpu_0']), + }) + + win_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("read/e", game._entities['cpu_0']), + }) + quests.append(Quest(win_events=[win_quest], fail_events=[fail_quest])) + + # 2. Player is in US LAB to find Electronic Key 1 + win_quest = Event(conditions={game.new_fact("in", game._entities['k_0'], game._entities['I'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + # 3. Player is in Russian Module and take digital Key 1 and/or push the button + win_quest = Event(conditions={game.new_fact("pushed", game._entities['b_0']), + game.new_fact("worn", game._entities['l_0'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + fail_quest = Event(conditions={game.new_fact("pushed", game._entities['b_0']), + game.new_fact("takenoff", game._entities['l_0']), + game.new_fact("open", game._entities['d_2'])}) + quests.append(Quest(win_events=[], fail_events=[fail_quest])) + + # 4. Player is in Hatch room and wears the cloth + win_quest = Event(conditions={game.new_fact("worn", game._entities['l_0'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + # 5. Player goes outside + win_quest = Event(conditions={game.new_fact("at", game._entities['P'], game._entities['r_4'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + game.quests = quests + + return game.build() + + +def quest_design_difficult(game): + quests = [] + + # 1. Is the Player performing successful in the Sleeping Station + win_quest = Event(conditions={ + game.new_fact("at", game._entities['P'], game._entities['r_0']) + }) + quests.append(Quest(win_events=[win_quest], fail_events=[], reward=0)) + + fail_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("unread/e", game._entities['cpu_0']), + }) + + win_quest = Event(conditions={ + game.new_fact("event", game._entities['P'], game._entities['r_0']), + game.new_fact("at", game._entities['P'], game._entities['r_1']), + game.new_fact("open", game._entities['d_0']), + game.new_fact("read/e", game._entities['cpu_0']), + }) + quests.append(Quest(win_events=[win_quest], fail_events=[fail_quest])) + + # 2. Player is in US LAB to find Electronic Key 1 + win_quest = Event(conditions={game.new_fact("in", game._entities['k_0'], game._entities['I'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + + # 3. Player is in Russian Module and take digital Key 1 and/or push the button + win_quest = Event(conditions={game.new_fact("in", game._entities['k_2'], game._entities['I'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + win_quest = Event(conditions={game.new_fact("pushed", game._entities['b_0']), + game.new_fact("worn", game._entities['l_0'])}) + quests.append(Quest(win_events=[win_quest], fail_events=[])) + fail_quest = Event(conditions={game.new_fact("pushed", game._entities['b_0']), + game.new_fact("takenoff", game._entities['l_0'])}) + quests.append(Quest(win_events=[], fail_events=[fail_quest])) + + # # 4. Player is the Control Mo/dule and take Electronic Key 2 + # win_quest = Event(conditions={game.new_fact("in", game._entities['k_5'], game._entities['I'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 5. Player reads the Secret Code book at Control Module + # win_quest = Event(conditions={game.new_fact("read/t", game._entities['txt_0'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 6. Player is in Hatch room and wears the cloth + # win_quest = Event(conditions={game.new_fact("worn", game._entities['l_0'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + # + # # 7. Player goes outside + # win_quest = Event(conditions={game.new_fact("at", game._entities['P'], game._entities['r_7'])}) + # quests.append(Quest(win_events=[win_quest], fail_events=[])) + + game.quests = quests + + return game.build() + + +# g = make_game_medium({'level': 'medium'}) + + +register(name="tw-spaceship", + desc="Generate a Spaceship challenge game", + make=make_game_medium, + add_arguments=build_argparser) diff --git a/textworld/challenges/spaceship/textworld_data/logic/CPU.twl b/textworld/challenges/spaceship/textworld_data/logic/CPU.twl new file mode 100644 index 00000000..6d6f8188 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/CPU.twl @@ -0,0 +1,53 @@ +# CPU-Like +type cpu : o { + predicates { + read/e(cpu); + unread/e(cpu); + } + + rules { + check/e1 :: $at(P, r) & $at(s, r) & $on(cpu, s) & unread/e(cpu) -> read/e(cpu); + check/e2 :: $at(P, r) & $in(cpu, I) & unread/e(cpu) -> read/e(cpu); + } + + constraints { + cpu2 :: read/e(cpu) & unread/e(cpu) -> fail(); + } + + inform7 { + type { + kind :: "CPU-like"; + definition :: "A CPU-like can be either read or unread. A CPU-like is usually unread."; + } + + predicates { + read/e(cpu) :: "The {cpu} is read"; + unread/e(cpu) :: "The {cpu} is unread"; + } + + commands { + check/e1 :: "check laptop for email" :: "checking email"; + check/e2 :: "check laptop for email" :: "checking email"; + } + + code :: """ + Understand the command "check" as something new. + Understand "check laptop for email" as checking email. + checking email is an action applying to nothing. + + Before checking email: + if a CPU-like (called pc) is read: + Say "You've already read all today's emails."; + rule fails; + otherwise: + if a random chance of 3 in 4 succeeds: + Say "No emails yet! Wait."; + rule fails. + + Carry out checking email: + if a CPU-like (called pc) is unread: + Say "Email: Your mission is started. You should go and check outside of the spaceship."; + Now the pc is read. + """; + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/cloth.twl b/textworld/challenges/spaceship/textworld_data/logic/cloth.twl new file mode 100644 index 00000000..042bc2ba --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/cloth.twl @@ -0,0 +1,61 @@ +# cloth +type l : o { + predicates { + worn(l); + takenoff(l); + clean(l); + dirty(l); + } + + rules { + wear/l :: in(l, I) & takenoff(l) -> worn(l); + takeoff/l :: worn(l) -> in(l, I) & takenoff(l); + + wash/l :: $at(l,r) & dirty(l) -> clean(l); + dirty/l :: $worn(l,P) & clean(l) -> dirty(l); + } + + reverse_rules { + wear/l :: takeoff/l; + wash/l :: dirty/l; + } + + constraints { + l1 :: clean(l) & dirty(l) -> fail(); + l2 :: worn(l) & takenoff(l) -> fail(); + } + + inform7 { + type { + kind :: "cloth-like"; + definition :: "cloth-like are wearable. cloth-like can be either clean or dirty. cloth-like are usually clean. cloth-like can be either worn in or worn out. cloth-like are usually worn out."; + } + + predicates { + worn(l) :: "The {l} is worn in"; + takenoff(l) :: "The {l} is worn out"; + clean(l) :: "The {l} is clean"; + dirty(l) :: "The {l} is dirty"; + } + + commands { + wear/l :: "wear {l}" :: "_wearing the {l}"; + takeoff/l :: "take off {l}" :: "taking off the {l}"; + + clean/l :: "clean {l}" :: "cleaning the {l}"; + dirty/l :: "dirty {l}" :: "dirtying the {l}"; + } + + code :: """ + Understand the command "wear" as something new. + Understand "wear [something]" as _wearing. + _wearing is an action applying to a thing. + + Carry out _wearing: + if a cloth-like (called cl) is worn out: + Now the cl is worn in; + otherwise: + Say "You have this cloth on.". + """; + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/container.twl b/textworld/challenges/spaceship/textworld_data/logic/container.twl new file mode 100644 index 00000000..11212489 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/container.twl @@ -0,0 +1,66 @@ +# container +type c : t { + 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); + + lock/bx :: $at(P, r) & $at(s, r) & $on(c, s) & $in(k, I) & $match(k, c) & closed(c) -> locked(c); + unlock/bx :: $at(P, r) & $at(s, r) & $on(c, s) & $in(k, I) & $match(k, c) & locked(c) -> closed(c); + + open/bx :: $at(P, r) & $at(s, r) & $on(c, s) & closed(c) -> open(c); + close/bx :: $at(P, r) & $at(s, r) & $on(c, s) & open(c) -> closed(c); + } + + reverse_rules { + lock/c :: unlock/c; + open/c :: close/c; + lock/bx :: unlock/bx; + open/bx :: close/bx; + } + + constraints { + c1 :: open(c) & closed(c) -> fail(); + c2 :: open(c) & locked(c) -> fail(); + c3 :: closed(c) & locked(c) -> fail(); + } + + inform7 { + type { + kind :: "container"; + definition :: "containers are openable, lockable and fixed in place. containers are usually closed."; + } + + 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}"; + + open/bx :: "open {c}" :: "opening the {c}"; + close/bx :: "close {c}" :: "closing the {c}"; + + lock/bx :: "lock {c} with {k}" :: "locking the {c} with the {k}"; + unlock/bx :: "unlock {c} with {k}" :: "unlocking the {c} with the {k}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/door.twl b/textworld/challenges/spaceship/textworld_data/logic/door.twl new file mode 100644 index 00000000..44330748 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/door.twl @@ -0,0 +1,89 @@ +# door +type d : t { + predicates { + open(d); + closed(d); + locked(d); + + 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); + + 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); + + lock/close/db :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & $in(b, c) & $pair(b, d) & pushed(b) & open(d) & free(r, r') & free(r', r) -> unpushed(b) & locked(d); + unlock/open/db :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & $in(b, c) & $pair(b, d) & unpushed(b) & locked(d) -> pushed(b) & open(d) & free(r, r') & free(r', r); + + lock/close/d/b :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & $in(b, c) & $pair(b, d) & $link(r', d, r'') & $link(r'', d, r') & pushed(b) & open(d) & free(r', r'') & free(r'', r') -> unpushed(b) & locked(d); + unlock/open/d/b :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & $in(b, c) & $pair(b, d) & $link(r', d, r'') & $link(r'', d, r') & unpushed(b) & locked(d) -> pushed(b) & open(d) & free(r', r'') & free(r'', r'); + + examine/d :: at(P, r) & $link(r, d, r') -> at(P, r); # Nothing changes. + } + + reverse_rules { + lock/d :: unlock/d; + open/d :: close/d; + lock/close/d/b :: unlock/open/d/b; + lock/close/db :: unlock/open/db; + } + + 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(); + + # There's already a door linking two rooms. + link3 :: link(r, d, r') & link(r, d', r') -> fail(); + + # 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(); + dr3 :: free(r, r1: r) & free(r, r2: r) & free(r, r3: r) & link(r, d4: d, r4: r) & link(r, d5: d, r5: r) -> fail(); + dr4 :: free(r, r1: r) & free(r, r2: r) & free(r, r3: r) & free(r, r4: r) & link(r, d5: d, r5: r) -> fail(); + + free1 :: link(r, d, r') & free(r, r') & closed(d) -> fail(); + free2 :: link(r, d, r') & free(r, r') & locked(d) -> fail(); + } + + inform7 { + type { + kind :: "door"; + 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"; + + link(r, d, r') :: ""; # No equivalent in Inform7. + } + + commands { + 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}"; + + lock/close/d/b :: "push {b}" :: "_pushing the {b}"; + unlock/open/d/b :: "push {b}" :: "_pushing the {b}"; + + lock/close/db :: "push {b}" :: "_pushing the {b}"; + unlock/open/db :: "push {b}" :: "_pushing the {b}"; + + examine/d :: "examine {d}" :: "examining the {d}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/food.twl b/textworld/challenges/spaceship/textworld_data/logic/food.twl new file mode 100644 index 00000000..5518c321 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/food.twl @@ -0,0 +1,34 @@ +# food +type f : o { + predicates { + edible(f); + eaten(f); + } + + rules { + eat :: in(f, I) -> eaten(f); + } + + constraints { + eaten1 :: eaten(f) & in(f, I) -> fail(); + eaten2 :: eaten(f) & in(f, c) -> fail(); + eaten3 :: eaten(f) & on(f, s) -> fail(); + eaten4 :: eaten(f) & at(f, r) -> fail(); + } + + inform7 { + type { + kind :: "food"; + definition :: "food is edible."; + } + + predicates { + edible(f) :: "The {f} is edible"; + eaten(f) :: "The {f} is nowhere"; + } + + commands { + eat :: "eat {f}" :: "eating the {f}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/inventory.twl b/textworld/challenges/spaceship/textworld_data/logic/inventory.twl new file mode 100644 index 00000000..696f0f7f --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/inventory.twl @@ -0,0 +1,72 @@ +# Inventory +type I { + predicates { + in(o, I); + } + + rules { + inventory :: at(P, r) -> at(P, r); # Nothing changes. + + take :: $at(P, r) & at(o, r) -> in(o, I); + + take/c :: $at(P, r) & $at(c, r) & $open(c) & in(o, c) -> in(o, I); + insert/c :: $at(P, r) & $at(c, r) & $open(c) & in(o, I) -> in(o, c); + + take/cs :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & in(o, c) -> in(o, I); + insert/cs :: $at(P, r) & $at(s, r) & $on(c, s) & $open(c) & in(o, I) -> in(o, c); + + take/s :: $at(P, r) & $at(s, r) & on(o, s) -> in(o, I); + hook :: $at(P, r) & $at(s, r) & in(o, I) -> on(o, s); + + examine/I :: in(o, I) -> in(o, I); # Nothing changes. + examine/s :: at(P, r) & $at(s, r) & $on(o, s) -> at(P, r); # Nothing changes. + examine/c :: at(P, r) & $at(c, r) & $open(c) & $in(o, c) -> at(P, r); # Nothing changes. + examine/or :: at(P, r) & $in(o, r) -> at(P, r); # Nothing changes. + examine/oc :: at(P, r) & $at(c, r) & $open(c) & $in(o, c) -> at(P, r); # Nothing changes. + examine/os :: at(P, r) & $at(s, r) & $on(o, s) -> at(P, r); # Nothing changes. + } + + reverse_rules { + inventory :: inventory; + + take/c :: insert/c; + take/s :: hook; + take/cs :: insert/cs; + + examine/I :: examine/I; + examine/s :: examine/s; + examine/c :: examine/c; + examine/or :: examine/or; + examine/oc :: examine/oc; + examine/os :: examine/os; + } + + inform7 { + predicates { + in(o, I) :: "The player carries the {o}"; + } + + commands { + + inventory :: "inventory" :: "taking inventory"; + + take :: "take {o}" :: "taking the {o}"; + + take/c :: "take {o} from {c}" :: "removing the {o} from the {c}"; + insert/c :: "insert {o} into {c}" :: "inserting the {o} into the {c}"; + + take/cs :: "take {o} from {c}" :: "removing the {o} from the {c}"; + insert/cs :: "insert {o} into {c}" :: "inserting the {o} into the {c}"; + + take/s :: "take {o} from {s}" :: "removing the {o} from the {s}"; + hook :: "hook {o} on {s}" :: "hooking the {o} on the {s}"; + + examine/I :: "examine {o}" :: "examining the {o}"; + examine/s :: "examine {o}" :: "examining the {o}"; + examine/c :: "examine {o}" :: "examining the {o}"; + examine/or :: "examine {o}" :: "examining the {o}"; + examine/oc :: "examine {o}" :: "examining the {o}"; + examine/os :: "examine {o}" :: "examining the {o}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/key.twl b/textworld/challenges/spaceship/textworld_data/logic/key.twl new file mode 100644 index 00000000..ff6d0499 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/key.twl @@ -0,0 +1,25 @@ +# key +type k : o { + predicates { + match(k, c); + match(k, d); + } + + constraints { + k1 :: match(k, c) & match(k', c) -> fail(); + k2 :: match(k, c) & match(k, c') -> fail(); + k3 :: match(k, d) & match(k', d) -> fail(); + k4 :: match(k, d) & match(k, d') -> fail(); + } + + inform7 { + type { + kind :: "key"; + } + + predicates { + match(k, c) :: "The matching key of the {c} is the {k}"; + match(k, d) :: "The matching key of the {d} is the {k}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/object.twl b/textworld/challenges/spaceship/textworld_data/logic/object.twl new file mode 100644 index 00000000..19a47080 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/object.twl @@ -0,0 +1,21 @@ +# object +type o : t { + constraints { + obj1 :: in(o, I) & in(o, c) -> fail(); + obj2 :: in(o, I) & on(o, s) -> fail(); + obj3 :: in(o, I) & at(o, r) -> fail(); + obj4 :: in(o, c) & on(o, s) -> fail(); + obj5 :: in(o, c) & at(o, r) -> fail(); + obj6 :: on(o, s) & at(o, r) -> fail(); + obj7 :: at(o, r) & at(o, r') -> fail(); + obj8 :: in(o, c) & in(o, c') -> fail(); + obj9 :: on(o, s) & on(o, s') -> fail(); + } + + inform7 { + type { + kind :: "object-like"; + definition :: "object-like is portable."; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/player.twl b/textworld/challenges/spaceship/textworld_data/logic/player.twl new file mode 100644 index 00000000..3f2b4ed6 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/player.twl @@ -0,0 +1,14 @@ +# Player +type P { + rules { + look :: at(P, r) -> at(P, r); # Nothing changes. + #wear/cloth :: $at(P,r) & at(l,r) -> on(l,P); + } + + inform7 { + commands { + look :: "look" :: "looking"; + #wear/cloth :: "wear cloth" :: "wearing the cloth"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/push_button.twl b/textworld/challenges/spaceship/textworld_data/logic/push_button.twl new file mode 100644 index 00000000..e9ff7ccb --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/push_button.twl @@ -0,0 +1,52 @@ +# push button +type b : t { + predicates { + pushed(b); + unpushed(b); + + pair(b, d); + + in(b, c); + } + + inform7 { + type { + kind :: "button-like"; + definition :: "A button-like can be either pushed or unpushed. A button-like is usually unpushed. A button-like is fixed in place."; + } + + predicates { + pushed(b) :: "The {b} is pushed"; + unpushed(b) :: "The {b} is unpushed"; + + pair(b, d) :: "The {b} pairs to {d}"; + + in(b, c) :: "The {b} is in the {c}"; + } + + code :: """ + connectivity relates a button-like to a door. The verb to pair to means the connectivity relation. + + Understand the command "push" as something new. + Understand "push [something]" as _pushing. + _pushing is an action applying to a thing. + + Carry out _pushing: + if a button-like (called pb) pairs to door (called dr): + if dr is locked: + Now the pb is pushed; + Now dr is unlocked; + Now dr is open; + otherwise: + Now the pb is unpushed; + Now dr is locked. + + Report _pushing: + if a button-like (called pb) pairs to door (called dr): + if dr is unlocked: + say "You push the [pb], and [dr] is now open."; + otherwise: + say "You push the [pb] again, and [dr] is now locked." + """; + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/room.twl b/textworld/challenges/spaceship/textworld_data/logic/room.twl new file mode 100644 index 00000000..58715688 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/room.twl @@ -0,0 +1,82 @@ +# room +type r { + predicates { + at(P, r); + at(t, r); + + north_of(r, r); + west_of(r, r); + + north_of/d(r, d, r); + west_of/d(r, d, r); + + free(r, r); + + south_of(r, r') = north_of(r', r); + east_of(r, r') = west_of(r', 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); + } + + rules { + go/north :: at(P, r) & $north_of(r', r) & $free(r, r') & $free(r', r) -> at(P, r'); + go/south :: at(P, r) & $south_of(r', r) & $free(r, r') & $free(r', r) -> at(P, r'); + go/east :: at(P, r) & $east_of(r', r) & $free(r, r') & $free(r', r) -> at(P, r'); + go/west :: at(P, r) & $west_of(r', r) & $free(r, r') & $free(r', r) -> at(P, r'); + } + + reverse_rules { + go/north :: go/south; + go/west :: go/east; + } + + constraints { + r1 :: at(P, r) & at(P, r') -> fail(); + r2 :: at(s, r) & at(s, r') -> fail(); + r3 :: at(c, r) & at(c, r') -> fail(); + + # An exit direction can only lead to one room. + nav_rr1 :: north_of(r, r') & north_of(r'', r') -> fail(); + nav_rr2 :: south_of(r, r') & south_of(r'', r') -> fail(); + nav_rr3 :: east_of(r, r') & east_of(r'', r') -> fail(); + nav_rr4 :: west_of(r, r') & west_of(r'', r') -> fail(); + + # Two rooms can only be connected once with each other. + nav_rrA :: north_of(r, r') & south_of(r, r') -> fail(); + nav_rrB :: north_of(r, r') & west_of(r, r') -> fail(); + nav_rrC :: north_of(r, r') & east_of(r, r') -> fail(); + nav_rrD :: south_of(r, r') & west_of(r, r') -> fail(); + nav_rrE :: south_of(r, r') & east_of(r, r') -> fail(); + nav_rrF :: west_of(r, r') & east_of(r, r') -> fail(); + } + + inform7 { + type { + kind :: "room"; + } + + predicates { + at(P, r) :: "The player is in {r}"; + at(t, r) :: "The {t} is in {r}"; + free(r, r') :: ""; # No equivalent in Inform7. + + north_of(r, r') :: "The {r} is mapped north of {r'}"; + south_of(r, r') :: "The {r} is mapped south of {r'}"; + east_of(r, r') :: "The {r} is mapped east of {r'}"; + west_of(r, r') :: "The {r} is mapped west of {r'}"; + + 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}"; + } + + commands { + go/north :: "go north" :: "going north"; + go/south :: "go south" :: "going south"; + go/east :: "go east" :: "going east"; + go/west :: "go west" :: "going west"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/supporter.twl b/textworld/challenges/spaceship/textworld_data/logic/supporter.twl new file mode 100644 index 00000000..3c8279a3 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/supporter.twl @@ -0,0 +1,19 @@ +# supporter +type s : t { + predicates { + on(o, s); + on(c, s); + } + + inform7 { + type { + kind :: "supporter"; + definition :: "supporters are fixed in place."; + } + + predicates { + on(o, s) :: "The {o} is on the {s}"; + on(c, s) :: "The {c} is on the {s}"; + } + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/text.twl b/textworld/challenges/spaceship/textworld_data/logic/text.twl new file mode 100644 index 00000000..744f82c2 --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/text.twl @@ -0,0 +1,48 @@ +# text-Like +type txt : o { + predicates { + read/t(txt); + unread/t(txt); + } + + rules { + read/book :: $at(P, r) & $in(txt, I) & unread/t(txt) -> read/t(txt); + examine/book :: at(P, r) & $in(txt, I) -> at(P, r); # Nothing changes. + } + + reverse_rules { + examine/book :: examine/book; + } + + constraints { + txt1 :: read/t(txt) & unread/t(txt) -> fail(); + } + + inform7 { + type { + kind :: "text-like"; + definition :: "A text-like can be either read or unread. A text-like is usually unread."; + } + + predicates { + read/t(txt) :: "The {txt} is read"; + unread/t(txt) :: "The {txt} is unread"; + } + + commands { + read/book :: "read the {txt}" :: "_reading the {txt}"; + examine/book :: "examine {txt}" :: "examining the {txt}"; + } + + code :: """ + Understand the command "read" as something new. + Understand "read [something]" as _reading. + _reading is an action applying to a thing. + + Carry out _reading: + if a text-like (called tx) is unread: + Say "You read the book and realized about that crucial hint."; + Now the tx is read; + """; + } +} diff --git a/textworld/challenges/spaceship/textworld_data/logic/thing.twl b/textworld/challenges/spaceship/textworld_data/logic/thing.twl new file mode 100644 index 00000000..042d167a --- /dev/null +++ b/textworld/challenges/spaceship/textworld_data/logic/thing.twl @@ -0,0 +1,34 @@ +# thing +type t { + predicates { + event(P, r); + } + + rules { + examine/t :: at(P, r) & $at(t, r) -> at(P, r); + } + + reverse_rules { + examine/t :: examine/t; + } + + inform7 { + type { + kind :: "thing"; + } + + predicates { + event(P, r) :: "the player was in {r}"; + } + commands { + examine/t :: "examine {t}" :: "examining the {t}"; + } + + code :: """ + Understand "tw-set seed [a number]" as updating the new seed. + Updating the new seed is an action applying to a number. + Carry out updating the new seed: + seed the random-number generator with the number understood. + """; + } +} diff --git a/textworld/generator/__init__.py b/textworld/generator/__init__.py index 0f300807..069379e8 100644 --- a/textworld/generator/__init__.py +++ b/textworld/generator/__init__.py @@ -159,9 +159,9 @@ def make_quest(world: Union[World, State], options: Optional[GameOptions] = None return quests -def make_grammar(options: Mapping = {}, rng: Optional[RandomState] = None) -> Grammar: +def make_grammar(options: Mapping = {}, rng: Optional[RandomState] = None, kb: Optional[KnowledgeBase] = None) -> Grammar: rng = g_rng.next() if rng is None else rng - grammar = Grammar(options, rng) + grammar = Grammar(options, rng, kb) grammar.check() return grammar diff --git a/textworld/generator/game.py b/textworld/generator/game.py index ccae041a..6ee51362 100644 --- a/textworld/generator/game.py +++ b/textworld/generator/game.py @@ -990,6 +990,9 @@ def update(self, action: Action) -> None: for quest_progression in self.quest_progressions: quest_progression.update(action, self.state) + for win_event in quest_progression.win_events: + self.state.apply(win_event.event.condition) + class GameOptions: """ diff --git a/textworld/generator/inform7/world2inform7.py b/textworld/generator/inform7/world2inform7.py index 386a5d83..38889cc1 100644 --- a/textworld/generator/inform7/world2inform7.py +++ b/textworld/generator/inform7/world2inform7.py @@ -311,6 +311,7 @@ def gen_source(self, seed: int = 1234) -> str: objective = self.game.objective.replace("\n", "[line break]") maximum_score = 0 + wining = 0 for quest_id, quest in enumerate(self.game.quests): maximum_score += quest.reward @@ -350,6 +351,7 @@ def gen_source(self, seed: int = 1234) -> str: quest_ending_conditions += win_template.format(conditions=conditions, reward=quest.reward, quest_id=quest_id) + wining += 1 quest_ending = """\ Every turn:\n{conditions} @@ -358,13 +360,14 @@ def gen_source(self, seed: int = 1234) -> str: source += textwrap.dedent(quest_ending) # Enable scoring is at least one quest has nonzero reward. - if maximum_score != 0: + if maximum_score >= 0: source += "Use scoring. The maximum score is {}.\n".format(maximum_score) # Build test condition for winning the game. game_winning_test = "1 is 0 [always false]" - if len(self.game.quests) > 0: - game_winning_test = "score is maximum score" + if wining > 0: + if maximum_score != 0: + game_winning_test = "score is maximum score" # Remove square bracket when printing score increases. Square brackets are conflicting with # Inform7's events parser in tw_inform7.py. diff --git a/textworld/generator/text_grammar.py b/textworld/generator/text_grammar.py index 59af474d..acaf22de 100644 --- a/textworld/generator/text_grammar.py +++ b/textworld/generator/text_grammar.py @@ -132,7 +132,8 @@ class Grammar: _cache = {} - def __init__(self, options: Union[GrammarOptions, Mapping[str, Any]] = {}, rng: Optional[RandomState] = None): + def __init__(self, options: Union[GrammarOptions, Mapping[str, Any]] = {}, rng: Optional[RandomState] = None, + kb: Optional[KnowledgeBase] = None): """ Arguments: options: @@ -159,6 +160,10 @@ def __init__(self, options: Union[GrammarOptions, Mapping[str, Any]] = {}, rng: # Load the object names file path = pjoin(KnowledgeBase.default().text_grammars_path, glob.escape(self.theme) + "*.twg") files = glob.glob(path) + if kb is not None: + path = pjoin(kb.text_grammars_path, glob.escape(self.theme) + "*.twg") + files += glob.glob(path) + if len(files) == 0: raise MissingTextGrammar(path)