#!/usr/bin/python # -*- coding: utf-8 -*- """‘Single-user dungeon’: mazmorra de un solo usuario. Un hack de un par de horas para la noche del sábado. Espero que lo disfruten. Esto es un lienzo para hacer una ficción interactiva *mínima* como un Wiki. Los objetos del juego participan en una jerarquía de contención, muchas veces siendo contenido dentro de algún contenedor, tal como una habitación o el jugador; y tienen nombres y descripciones. El mundo es transparentemente persistente a través de pickle. Los comandos incluyen: - mirar - ver objeto - norte, sur, este, oeste, arriba, bajar (abreviaciones n, s, e, o, a, b) - renombrar objeto nuevonombre - describir objeto como larga descripción - crear objeto - tomar objeto - inventario - soltar objeto - cavar {norte, sur, etc.} ?a lugar? Cavar sin especificar un destino crea un nuevo lugar. Así podés crear tu mundo habitación por habitación y describir sus objetos. Lo que no podés hacer todavía es destruir o programar comportamientos. Ejemplo, empezando con un mundo vacío: El vacío Estás en un lugar borroso, sin mucha forma visible. El vacío contiene: vos. ? renombrar aca Entrada Ahora aca ya no es “El vacío” sino “Entrada”. ? describir Entrada como Hay muros azules y un techo dorado, con azulejos de patrones geométricos bajo tus pies. Descripción de “Entrada” cambiado. ? mirar Entrada Hay muros azules y un techo dorado, con azulejos de patrones geométricos bajo tus pies. Entrada contiene: vos. ? cavar norte Otro vacío Estás en otra habitación sin características particulares. Otro vacío contiene: vos. Hay salida hacia el sur. ? sur Entrada Hay muros azules y un techo dorado, con azulejos de patrones geométricos bajo tus pies. Entrada contiene: vos. Hay salida hacia el norte. ? n Otro vacío Estás en otra habitación sin características particulares. Otro vacío contiene: vos. Hay salida hacia el sur. ? renombrar aca como Patio central Ahora aca ya no es “Otro vacío” sino “como Patio central”. ? renombrar aca Patio central Ahora aca ya no es “como Patio central” sino “Patio central”. ? describir aca como El cielo arriba es azul con pocas nubes; abajo hay adoquines con cesped subiendo entre ellos. Hay una fuente seca en el centro del patio, ya muchos años sin agua. Descripción de “Patio central” cambiado. ? mirar Patio central El cielo arriba es azul con pocas nubes; abajo hay adoquines con cesped subiendo entre ellos. Hay una fuente seca en el centro del patio, ya muchos años sin agua. Patio central contiene: vos. Hay salida hacia el sur. ? crear palomas Ahora hay un palomas acá. ? crear mate Ahora hay un mate acá. ? describir mate como Es un mate estilo gaucho, hecho con la pata de una vaca. Descripción de “mate” cambiado. ? ver mate mate Es un mate estilo gaucho, hecho con la pata de una vaca. ? mirar Patio central El cielo arriba es azul con pocas nubes; abajo hay adoquines con cesped subiendo entre ellos. Hay una fuente seca en el centro del patio, ya muchos años sin agua. Patio central contiene: vos, palomas, y mate. Hay salida hacia el sur. ? tomar palomas Tomado. ? cavar oeste Otro vacío Estás en otra habitación sin características particulares. Otro vacío contiene: vos. Hay salida hacia el este. ? cavar sur a Entrada Entrada Hay muros azules y un techo dorado, con azulejos de patrones geométricos bajo tus pies. Entrada contiene: vos. Hay salida hacia el norte. ? n Patio central El cielo arriba es azul con pocas nubes; abajo hay adoquines con cesped subiendo entre ellos. Hay una fuente seca en el centro del patio, ya muchos años sin agua. Patio central contiene: mate y vos. Hay salida hacia el sur y oeste. ? o Otro vacío Estás en otra habitación sin características particulares. Otro vacío contiene: vos. Hay salida hacia el este y sur. ? s Entrada Hay muros azules y un techo dorado, con azulejos de patrones geométricos bajo tus pies. Entrada contiene: vos. Hay salida hacia el norte. ? dejar palomas Soltado. ? ver palomas palomas No ves nada muy interesante. Podés escribir “describir palomas como Algo muy interesante.” """ import re, readline, os, traceback, textwrap try: import cPickle as pickle except ImportError: import pickle try: input = raw_input except NameError: pass def y_list(words): words = list(words) if len(words) == 1: return words[0] if len(words) == 2: return ' y '.join(words) return ', '.join(words[:-1] + ['y ' + words[-1]]) class Thing(object): def __init__(self, name, description, container=None): self.name = name self.aliases = set() self.description = description self.contents = [] self.container = container if container is not None: container.add(self) def is_a(self, name): return name == self.name or name in self.aliases def move_to(self, container): if self.container: self.container.remove(self) self.container = container container.add(self) def remove(self, item): self.contents.remove(item) def add(self, item): self.contents.append(item) def describe(self): content_string = '' if self.contents: content_string = ('\n\n' + self.name + ' contiene: '.format(self.name) + y_list(obj.name for obj in self.contents) + '.') return '{}\n\n{}{}'.format(self.name, self.description, content_string) def find_thing(player, room, name): for thing in player.contents: if thing.is_a(name): return thing for thing in room.contents: if thing.is_a(name): return thing if room.is_a(name): return room return None def find_thing_vocally(player, room, name): thing = find_thing(player, room, name) if not thing: player.tell('No hay ningún ' + name + ' a la vista.') return thing class Room(Thing): def __init__(self, name, description): super(Room, self).__init__(name, description) self.exits = {} self.aliases.update(['aca', 'acá', 'aquí', 'habitación']) def go(self, player, direction): dest = self.exits.get(direction) if not dest: return False player.move_to(dest) player.tell(dest.describe()) return True def describe(self): desc = super(Room, self).describe() if self.exits: desc += '\n\nHay salida hacia el {}.'.format(y_list(self.exits.keys())) return desc class Player(Thing): def tell(self, text): print(wrap(text)) def wrap(text): paragraphs = text.split('\n\n') return '\n\n'.join('\n'.join(textwrap.wrap(para)) for para in paragraphs) verbs = {} def verb(fun): verbs[fun.__name__] = fun return fun @verb def mirar(world, args): world.player.tell(world.player.container.describe()) @verb def renombrar(world, args): name, newname = args.split(None, 1) player = world.player thing = find_thing_vocally(player, player.container, name) if not thing: return oldname = thing.name thing.name = newname player.tell('Ahora {} ya no es “{}” sino “{}”.'.format(name, oldname, newname)) @verb def cavar(world, args): player = world.player args = args.strip() direction = args.split()[0] if direction not in directions: player.tell('No sé dónde está {}; conozco los sentidos {}.'.format(direction, y_list(directions.keys()))) return d2 = directions[direction] dest = args[len(direction):].strip() if dest == '': new_room = Room('Otro vacío', 'Estás en otra habitación sin características particulares.') world.add_room(new_room) else: if not dest.startswith('a '): player.tell('No entiendo "{}" ({}, {}); podés escribir algo como “cavar norte a pasillo.”'.format(dest, direction, args)) return dest = dest[2:] new_room = world.find_room(dest) if not new_room: player.tell('No sé qué es "{}".'.format(dest)) return old_room = player.container old_room.exits[d2] = new_room reverse = antonym[d2] if reverse not in new_room.exits: new_room.exits[reverse] = old_room old_room.go(player, d2) @verb def describir(world, args): try: name, como, desc = args.split(None, 2) except ValueError: return explain_describir(world) if como != 'como': return explain_describir(world) player = world.player thing = find_thing_vocally(player, player.container, name) if not thing: return thing.description = desc player.tell("Descripción de “{}” cambiado.".format(thing.name)) def explain_describir(world): world.player.tell('No entiendo; podés decir cosas como “describir aca como Hay un atardecer visible al oeste.”') @verb def ver(world, args): player = world.player thing = find_thing_vocally(player, player.container, args.strip()) if not thing: return player.tell(thing.describe()) @verb def crear(world, args): name = args.strip() player = world.player thing = Thing(name, "No ves nada muy interesante. Podés escribir “describir {} como Algo muy interesante.”".format(name), container=player.container) player.tell("Ahora hay un {} acá.".format(name)) @verb def tomar(world, args): player = world.player thing = find_thing_vocally(player, player.container, args.strip()) if not thing: return thing.move_to(player) player.tell("Tomado.") @verb def inventario(world, args): player = world.player player.tell("Tenés:") for item in player.contents: player.tell("- {}".format(item.name)) @verb def soltar(world, args): player = world.player thing = find_thing_vocally(player, player.container, args.strip()) if not thing: return thing.move_to(player.container) player.tell("Soltado.") verbs['dejar'] = soltar directions = {'n': 'norte', 's': 'sur', 'o': 'oeste', 'e': 'este', 'a': 'arriba', 'b': 'bajar', 'abajo': 'bajar', 'subir': 'arriba'} for v in list(directions.values()): directions[v] = v antonym = {} for k, v in [('norte', 'sur'), ('este', 'oeste'), ('arriba', 'bajar')]: antonym[k] = v antonym[v] = k def ir_verb(direction): def ir_verb_fun(world, args): d2 = directions[direction] player = world.player room = player.container if not room.go(player, d2): player.tell("No hay salida de acá hacia el {}; podés “cavar {}”.".format(d2, d2)) return ir_verb_fun for v in directions: verbs[v] = ir_verb(v) class World: def __init__(self, player, rooms): self.player = player self.rooms = rooms def add_room(self, room): self.rooms.append(room) def find_room(self, name): for room in self.rooms: if room.is_a(name): return room def basic_world(): room = Room('El vacío', 'Estás en un lugar borroso, sin mucha forma visible.') player = Player('vos', 'Sos alguna suerte de ser vivo.', container=room) player.aliases.update(['yo', 'mi', 'mí']) return World(player, [room]) world_filename = 'mundosud.pck' def save_world(world): tmpname = 'tmp.' + world_filename with open(tmpname, 'wb') as f: pickle.dump(world, f) f.flush() os.fsync(f.fileno()) # hey, it’s 2020, this is okay, right? os.rename(tmpname, world_filename) def load_world(): with open(world_filename, 'rb') as f: return pickle.load(f) def repl(): try: world = load_world() except IOError: world = basic_world() mirar(world, '') while True: line = input('? ') try: verb = line.split()[0] if verb in verbs: verbs[verb](world, line[len(verb):]) else: world.player.tell('No entiendo “{}”. '.format(line)) except EOFError: break except Exception: traceback.print_exc() world = load_world() else: save_world(world) # Save the world! if __name__ == '__main__': repl()