diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/interpreter.py b/src/interpreter.py index 86cd37e..6db846e 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -12,67 +12,134 @@ from world import World -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - - -class OptionCommand(object): - ''' - This is the super class for all option commands. (A define command - includes one or more option commands.) - ''' - - def __init__(self, name, doc_string, arg): - self.name = name - self.doc_string = doc_string - self.arg = arg - - def act(self): - ''' - Carry out this option commands action with argument arg. - Has to be extended by subclasses! - ''' - raise NotImplementedError - - class DefineCommand(object): ''' This is the super class for all define commands. It should be extended for each individual command, like define-place, define-monster, etc. + + Each define command works by creating the relevant object, then fleshing + it out as more options are passed to it. Finally, the finished object is + returned. ''' def __init__(self, name, doc_string): self.name = name self.doc_string = doc_string - self.option_registry = [] + self.option_registry = dict() - def add_option(self, option_command): - self.option_registry.append(option_command) - - def execute(self): + def init_object(self, object_name): ''' - Execute this command with all its options + Initialize the object this command creates ''' - for o in self.option_registry: - o.act(arg) + raise NotImplementedError + + def return_object(self): + ''' + Return the type of the game object ('place', 'npc', 'item' or + 'monster') and the object itself + ''' + raise NotImplementedError + + def pass_option(self, option_name, option_argument): + ''' + Pass this define command one of its options and the relevant argument + ''' + self.option_registry[option_name]["function"](option_argument) + + def add_option(self, option_name, option_docstring, option_function): + ''' + Add an option for this define command. Arguments: + option_name: The name of this option as it would appear in + an atl source file + option_docstring: A description of this option + option_function: The function that this option calls + (this function should take exactly one string argument) + ''' + option_dict = dict["docstring":option_docstring, + "function":option_function] + self.option_registry[option_name] = option_dict -define_command_registry = [] +define_command_registry = dict() def register_define_command(def_com): - define_command_registry.append(def_com) + define_command_registry[def_com.name] = def_com class Parser(object): ''' The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands. + a series of DefineCommands, which are then executed ''' - def __init__(self, world_file): - self.world_file = world_file + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.load(world_file_name) + return self.world - def generate_world(self): - return World() + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + + def add_line_commands(self): + ''' + Line commands are ATL constructs that only take up one line + (unlike define commands). All line commands must be registered in + this method by adding them to the line_command_registry and setting + their key-value to the method to be called when their key appears + in an ATL source file. + ''' + self.line_command_registry["load"] = self.load + self.line_command_registry["start-place"] = self.world.set_starting_place + + def interpret_atl(self, atl_source): + ''' + This method interprets a list of atl source lines, passing them + on to the relevant define commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + line_no = line_no + 1 + if line[0] == "#": + pass #comments are ignored + elif (line == "" or line == "\n") and define_command: + # Empty lines finish off define blocks + object_type, game_object = define_command.return_object() + self.world.add_object(object_type, game_object) + define_command = None + else: + print("Line: '"+line+"'") + command = line.split()[0] + # execute a line command + if command in line_command_registry.keys(): + line_command_registry[command](line[line.find(" ")+1:]) + # start of a define block + elif command in define_command_registry.keys(): + define_command = define_command_registry[command] + object_name = line[line.find(" ")+1:] + define_command.init_object(object_name) + # parse an option command + elif line[0] == " " or line[0] == "\t": + while line[0] == " " or line[0] == "\t": + line = line[1:] + if line and define_command: + option_command = line.split()[0] + option_arg = line[line.find(" ")+1:] + define_command.pass_option(option_command, option_arg) + else: + # XXX: What should be done here? Do nothing, raise a + # syntax error, or something else? + print("Unrecognized syntax in line "+str(line_no)+"!") + diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/interpreter.py b/src/interpreter.py index 86cd37e..6db846e 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -12,67 +12,134 @@ from world import World -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - - -class OptionCommand(object): - ''' - This is the super class for all option commands. (A define command - includes one or more option commands.) - ''' - - def __init__(self, name, doc_string, arg): - self.name = name - self.doc_string = doc_string - self.arg = arg - - def act(self): - ''' - Carry out this option commands action with argument arg. - Has to be extended by subclasses! - ''' - raise NotImplementedError - - class DefineCommand(object): ''' This is the super class for all define commands. It should be extended for each individual command, like define-place, define-monster, etc. + + Each define command works by creating the relevant object, then fleshing + it out as more options are passed to it. Finally, the finished object is + returned. ''' def __init__(self, name, doc_string): self.name = name self.doc_string = doc_string - self.option_registry = [] + self.option_registry = dict() - def add_option(self, option_command): - self.option_registry.append(option_command) - - def execute(self): + def init_object(self, object_name): ''' - Execute this command with all its options + Initialize the object this command creates ''' - for o in self.option_registry: - o.act(arg) + raise NotImplementedError + + def return_object(self): + ''' + Return the type of the game object ('place', 'npc', 'item' or + 'monster') and the object itself + ''' + raise NotImplementedError + + def pass_option(self, option_name, option_argument): + ''' + Pass this define command one of its options and the relevant argument + ''' + self.option_registry[option_name]["function"](option_argument) + + def add_option(self, option_name, option_docstring, option_function): + ''' + Add an option for this define command. Arguments: + option_name: The name of this option as it would appear in + an atl source file + option_docstring: A description of this option + option_function: The function that this option calls + (this function should take exactly one string argument) + ''' + option_dict = dict["docstring":option_docstring, + "function":option_function] + self.option_registry[option_name] = option_dict -define_command_registry = [] +define_command_registry = dict() def register_define_command(def_com): - define_command_registry.append(def_com) + define_command_registry[def_com.name] = def_com class Parser(object): ''' The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands. + a series of DefineCommands, which are then executed ''' - def __init__(self, world_file): - self.world_file = world_file + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.load(world_file_name) + return self.world - def generate_world(self): - return World() + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + + def add_line_commands(self): + ''' + Line commands are ATL constructs that only take up one line + (unlike define commands). All line commands must be registered in + this method by adding them to the line_command_registry and setting + their key-value to the method to be called when their key appears + in an ATL source file. + ''' + self.line_command_registry["load"] = self.load + self.line_command_registry["start-place"] = self.world.set_starting_place + + def interpret_atl(self, atl_source): + ''' + This method interprets a list of atl source lines, passing them + on to the relevant define commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + line_no = line_no + 1 + if line[0] == "#": + pass #comments are ignored + elif (line == "" or line == "\n") and define_command: + # Empty lines finish off define blocks + object_type, game_object = define_command.return_object() + self.world.add_object(object_type, game_object) + define_command = None + else: + print("Line: '"+line+"'") + command = line.split()[0] + # execute a line command + if command in line_command_registry.keys(): + line_command_registry[command](line[line.find(" ")+1:]) + # start of a define block + elif command in define_command_registry.keys(): + define_command = define_command_registry[command] + object_name = line[line.find(" ")+1:] + define_command.init_object(object_name) + # parse an option command + elif line[0] == " " or line[0] == "\t": + while line[0] == " " or line[0] == "\t": + line = line[1:] + if line and define_command: + option_command = line.split()[0] + option_arg = line[line.find(" ")+1:] + define_command.pass_option(option_command, option_arg) + else: + # XXX: What should be done here? Do nothing, raise a + # syntax error, or something else? + print("Unrecognized syntax in line "+str(line_no)+"!") + diff --git a/src/place.py b/src/place.py index 8ffd706..eabd30e 100644 --- a/src/place.py +++ b/src/place.py @@ -9,7 +9,7 @@ # date: 02/05/2015 # -from parser import DefineCommand +from interpreter import DefineCommand, register_define_command class Place(object): @@ -18,13 +18,13 @@ players, monsters, items, NPCs, and links to other places. ''' - def __init__(self, name, description, + def __init__(self, name, description="", neighbours=[], monsters=[], npc=[], items=[]): ''' The constructor for a new place. Arguments: - name: the name of this place + name: the name of this place (compulsory) description: a description string neighbours: a list of place names of places bordering on this one monsters: a list of instances of monsters @@ -64,34 +64,33 @@ self.items.remove(item) -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - -class NeighbourOption(OptionCommand): - ''' - Add a new neighbour to the current place - ''' - - def __init__(self, arg, place): - OptionCommand.__init__("neighbour", - "Add the place given in the argument as a neighbour", - arg) - self.place = place - - def act(self, arg): - self.place.add_neighbour(arg) - - class DefinePlace(DefineCommand): ''' The Atlantis language construct to create a place. ''' - def __init__(self, place): + def __init__(self): DefineCommand.__init__("define-place", "Describe a new location in the game world") - self.place = place - self.add_option(NeighbourOption(self.place)) + self.add_option("description", + "Describe this place", + set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + add_neighbour) + + def init_object(self, place_name): + self.place = Place(name=place_name) + def return_object(self): + return 'place', self.place + + # This could probably be transformed into a lambda function + def add_neighbour(self, arg): + self.place.add_neighbour(arg) + + # ...same with this one + def set_description(self, arg): + self.place.set_description(arg) + +register_define_command(DefinePlace()) diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/interpreter.py b/src/interpreter.py index 86cd37e..6db846e 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -12,67 +12,134 @@ from world import World -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - - -class OptionCommand(object): - ''' - This is the super class for all option commands. (A define command - includes one or more option commands.) - ''' - - def __init__(self, name, doc_string, arg): - self.name = name - self.doc_string = doc_string - self.arg = arg - - def act(self): - ''' - Carry out this option commands action with argument arg. - Has to be extended by subclasses! - ''' - raise NotImplementedError - - class DefineCommand(object): ''' This is the super class for all define commands. It should be extended for each individual command, like define-place, define-monster, etc. + + Each define command works by creating the relevant object, then fleshing + it out as more options are passed to it. Finally, the finished object is + returned. ''' def __init__(self, name, doc_string): self.name = name self.doc_string = doc_string - self.option_registry = [] + self.option_registry = dict() - def add_option(self, option_command): - self.option_registry.append(option_command) - - def execute(self): + def init_object(self, object_name): ''' - Execute this command with all its options + Initialize the object this command creates ''' - for o in self.option_registry: - o.act(arg) + raise NotImplementedError + + def return_object(self): + ''' + Return the type of the game object ('place', 'npc', 'item' or + 'monster') and the object itself + ''' + raise NotImplementedError + + def pass_option(self, option_name, option_argument): + ''' + Pass this define command one of its options and the relevant argument + ''' + self.option_registry[option_name]["function"](option_argument) + + def add_option(self, option_name, option_docstring, option_function): + ''' + Add an option for this define command. Arguments: + option_name: The name of this option as it would appear in + an atl source file + option_docstring: A description of this option + option_function: The function that this option calls + (this function should take exactly one string argument) + ''' + option_dict = dict["docstring":option_docstring, + "function":option_function] + self.option_registry[option_name] = option_dict -define_command_registry = [] +define_command_registry = dict() def register_define_command(def_com): - define_command_registry.append(def_com) + define_command_registry[def_com.name] = def_com class Parser(object): ''' The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands. + a series of DefineCommands, which are then executed ''' - def __init__(self, world_file): - self.world_file = world_file + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.load(world_file_name) + return self.world - def generate_world(self): - return World() + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + + def add_line_commands(self): + ''' + Line commands are ATL constructs that only take up one line + (unlike define commands). All line commands must be registered in + this method by adding them to the line_command_registry and setting + their key-value to the method to be called when their key appears + in an ATL source file. + ''' + self.line_command_registry["load"] = self.load + self.line_command_registry["start-place"] = self.world.set_starting_place + + def interpret_atl(self, atl_source): + ''' + This method interprets a list of atl source lines, passing them + on to the relevant define commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + line_no = line_no + 1 + if line[0] == "#": + pass #comments are ignored + elif (line == "" or line == "\n") and define_command: + # Empty lines finish off define blocks + object_type, game_object = define_command.return_object() + self.world.add_object(object_type, game_object) + define_command = None + else: + print("Line: '"+line+"'") + command = line.split()[0] + # execute a line command + if command in line_command_registry.keys(): + line_command_registry[command](line[line.find(" ")+1:]) + # start of a define block + elif command in define_command_registry.keys(): + define_command = define_command_registry[command] + object_name = line[line.find(" ")+1:] + define_command.init_object(object_name) + # parse an option command + elif line[0] == " " or line[0] == "\t": + while line[0] == " " or line[0] == "\t": + line = line[1:] + if line and define_command: + option_command = line.split()[0] + option_arg = line[line.find(" ")+1:] + define_command.pass_option(option_command, option_arg) + else: + # XXX: What should be done here? Do nothing, raise a + # syntax error, or something else? + print("Unrecognized syntax in line "+str(line_no)+"!") + diff --git a/src/place.py b/src/place.py index 8ffd706..eabd30e 100644 --- a/src/place.py +++ b/src/place.py @@ -9,7 +9,7 @@ # date: 02/05/2015 # -from parser import DefineCommand +from interpreter import DefineCommand, register_define_command class Place(object): @@ -18,13 +18,13 @@ players, monsters, items, NPCs, and links to other places. ''' - def __init__(self, name, description, + def __init__(self, name, description="", neighbours=[], monsters=[], npc=[], items=[]): ''' The constructor for a new place. Arguments: - name: the name of this place + name: the name of this place (compulsory) description: a description string neighbours: a list of place names of places bordering on this one monsters: a list of instances of monsters @@ -64,34 +64,33 @@ self.items.remove(item) -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - -class NeighbourOption(OptionCommand): - ''' - Add a new neighbour to the current place - ''' - - def __init__(self, arg, place): - OptionCommand.__init__("neighbour", - "Add the place given in the argument as a neighbour", - arg) - self.place = place - - def act(self, arg): - self.place.add_neighbour(arg) - - class DefinePlace(DefineCommand): ''' The Atlantis language construct to create a place. ''' - def __init__(self, place): + def __init__(self): DefineCommand.__init__("define-place", "Describe a new location in the game world") - self.place = place - self.add_option(NeighbourOption(self.place)) + self.add_option("description", + "Describe this place", + set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + add_neighbour) + + def init_object(self, place_name): + self.place = Place(name=place_name) + def return_object(self): + return 'place', self.place + + # This could probably be transformed into a lambda function + def add_neighbour(self, arg): + self.place.add_neighbour(arg) + + # ...same with this one + def set_description(self, arg): + self.place.set_description(arg) + +register_define_command(DefinePlace()) diff --git a/src/server.py b/src/server.py index aaecbb4..8c6b6ea 100644 --- a/src/server.py +++ b/src/server.py @@ -8,7 +8,7 @@ # date: 02/05/2015 # -from parser import Parser +from interpreter import Parser #TODO: lock file for the server! @@ -22,5 +22,12 @@ print("The server is still under construction!") self.port = port self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.generate_world() + self.world = Parser(self.world_file) + test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("Place: "+self.world.get_place(p).name) + print("Neighbours: "+self.world.get_place(p).neighbours+"\n") diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/interpreter.py b/src/interpreter.py index 86cd37e..6db846e 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -12,67 +12,134 @@ from world import World -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - - -class OptionCommand(object): - ''' - This is the super class for all option commands. (A define command - includes one or more option commands.) - ''' - - def __init__(self, name, doc_string, arg): - self.name = name - self.doc_string = doc_string - self.arg = arg - - def act(self): - ''' - Carry out this option commands action with argument arg. - Has to be extended by subclasses! - ''' - raise NotImplementedError - - class DefineCommand(object): ''' This is the super class for all define commands. It should be extended for each individual command, like define-place, define-monster, etc. + + Each define command works by creating the relevant object, then fleshing + it out as more options are passed to it. Finally, the finished object is + returned. ''' def __init__(self, name, doc_string): self.name = name self.doc_string = doc_string - self.option_registry = [] + self.option_registry = dict() - def add_option(self, option_command): - self.option_registry.append(option_command) - - def execute(self): + def init_object(self, object_name): ''' - Execute this command with all its options + Initialize the object this command creates ''' - for o in self.option_registry: - o.act(arg) + raise NotImplementedError + + def return_object(self): + ''' + Return the type of the game object ('place', 'npc', 'item' or + 'monster') and the object itself + ''' + raise NotImplementedError + + def pass_option(self, option_name, option_argument): + ''' + Pass this define command one of its options and the relevant argument + ''' + self.option_registry[option_name]["function"](option_argument) + + def add_option(self, option_name, option_docstring, option_function): + ''' + Add an option for this define command. Arguments: + option_name: The name of this option as it would appear in + an atl source file + option_docstring: A description of this option + option_function: The function that this option calls + (this function should take exactly one string argument) + ''' + option_dict = dict["docstring":option_docstring, + "function":option_function] + self.option_registry[option_name] = option_dict -define_command_registry = [] +define_command_registry = dict() def register_define_command(def_com): - define_command_registry.append(def_com) + define_command_registry[def_com.name] = def_com class Parser(object): ''' The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands. + a series of DefineCommands, which are then executed ''' - def __init__(self, world_file): - self.world_file = world_file + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.load(world_file_name) + return self.world - def generate_world(self): - return World() + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + + def add_line_commands(self): + ''' + Line commands are ATL constructs that only take up one line + (unlike define commands). All line commands must be registered in + this method by adding them to the line_command_registry and setting + their key-value to the method to be called when their key appears + in an ATL source file. + ''' + self.line_command_registry["load"] = self.load + self.line_command_registry["start-place"] = self.world.set_starting_place + + def interpret_atl(self, atl_source): + ''' + This method interprets a list of atl source lines, passing them + on to the relevant define commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + line_no = line_no + 1 + if line[0] == "#": + pass #comments are ignored + elif (line == "" or line == "\n") and define_command: + # Empty lines finish off define blocks + object_type, game_object = define_command.return_object() + self.world.add_object(object_type, game_object) + define_command = None + else: + print("Line: '"+line+"'") + command = line.split()[0] + # execute a line command + if command in line_command_registry.keys(): + line_command_registry[command](line[line.find(" ")+1:]) + # start of a define block + elif command in define_command_registry.keys(): + define_command = define_command_registry[command] + object_name = line[line.find(" ")+1:] + define_command.init_object(object_name) + # parse an option command + elif line[0] == " " or line[0] == "\t": + while line[0] == " " or line[0] == "\t": + line = line[1:] + if line and define_command: + option_command = line.split()[0] + option_arg = line[line.find(" ")+1:] + define_command.pass_option(option_command, option_arg) + else: + # XXX: What should be done here? Do nothing, raise a + # syntax error, or something else? + print("Unrecognized syntax in line "+str(line_no)+"!") + diff --git a/src/place.py b/src/place.py index 8ffd706..eabd30e 100644 --- a/src/place.py +++ b/src/place.py @@ -9,7 +9,7 @@ # date: 02/05/2015 # -from parser import DefineCommand +from interpreter import DefineCommand, register_define_command class Place(object): @@ -18,13 +18,13 @@ players, monsters, items, NPCs, and links to other places. ''' - def __init__(self, name, description, + def __init__(self, name, description="", neighbours=[], monsters=[], npc=[], items=[]): ''' The constructor for a new place. Arguments: - name: the name of this place + name: the name of this place (compulsory) description: a description string neighbours: a list of place names of places bordering on this one monsters: a list of instances of monsters @@ -64,34 +64,33 @@ self.items.remove(item) -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - -class NeighbourOption(OptionCommand): - ''' - Add a new neighbour to the current place - ''' - - def __init__(self, arg, place): - OptionCommand.__init__("neighbour", - "Add the place given in the argument as a neighbour", - arg) - self.place = place - - def act(self, arg): - self.place.add_neighbour(arg) - - class DefinePlace(DefineCommand): ''' The Atlantis language construct to create a place. ''' - def __init__(self, place): + def __init__(self): DefineCommand.__init__("define-place", "Describe a new location in the game world") - self.place = place - self.add_option(NeighbourOption(self.place)) + self.add_option("description", + "Describe this place", + set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + add_neighbour) + + def init_object(self, place_name): + self.place = Place(name=place_name) + def return_object(self): + return 'place', self.place + + # This could probably be transformed into a lambda function + def add_neighbour(self, arg): + self.place.add_neighbour(arg) + + # ...same with this one + def set_description(self, arg): + self.place.set_description(arg) + +register_define_command(DefinePlace()) diff --git a/src/server.py b/src/server.py index aaecbb4..8c6b6ea 100644 --- a/src/server.py +++ b/src/server.py @@ -8,7 +8,7 @@ # date: 02/05/2015 # -from parser import Parser +from interpreter import Parser #TODO: lock file for the server! @@ -22,5 +22,12 @@ print("The server is still under construction!") self.port = port self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.generate_world() + self.world = Parser(self.world_file) + test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("Place: "+self.world.get_place(p).name) + print("Neighbours: "+self.world.get_place(p).neighbours+"\n") diff --git a/src/world.py b/src/world.py index 0c2fe32..aaacc17 100644 --- a/src/world.py +++ b/src/world.py @@ -8,6 +8,9 @@ # date: 02/05/2015 # +import copy + + class World(object): ''' The World class saves and gives access to the current state @@ -15,39 +18,86 @@ ''' def __init__(self): - self.places = [] - self.players = [] + ''' + The constructor initializes dicts consisting of name:object pairs + for all the places, players, NPCs, items and monsters available in + the world. + ''' + self.places = dict() + self.players = dict() self.starting_place = None - - def register_place(self, place): - if self.place not in places: - self.places.append(place) - else: - print("Attempted to add a place that already exists!") - + self.npc = dict() + self.items = dict() + self.monsters = dict() + def register_player(self, player): - if player not in self.players: - self.players.append(player) + if player.name not in self.players.keys(): if player.place == None: if self.starting_place: player.place = self.starting_place else: print("No starting place set!") + self.players[player.name] = player else: print("Attempted to add a player that already exists!") - def get_place(self, name): - for p in self.places: - if p.name == name: - return p + def add_object(self, object_type, game_object): + ''' + Add a game object to the world. Acts as a wrapper around add_place, etc. + object_type: 'place', 'npc', 'item' or 'monster' + game_object: the actual object + ''' + if object_type == "place": + add_place(game_object) + elif object_type == "npc": + add_npc(game_object) + elif object_type == "item": + add_item(game_object) + elif object_type == "monster": + add_monster(game_object) + + def add_place(self, place): + self.places[place.name] = place + + def add_monster(self, monster): + self.monsters[monster.name] = monster + + def add_npc(self, npc): + self.npc[npc.name] = npc + + def add_item(self, item): + self.items[item.name] = item def get_player(self, name): - for p in self.players: - if p.name == name: - return p + return self.players[name] - def set_starting_place(self, name): - for p in self.places: - if p.name == name: - self.starting_place = p + def get_place(self, name): + return self.places[name] + + def set_starting_place(self, place_name): + self.starting_place = place_name + def get_npc(self, npc_name): + return self.npc[npc_name] + + def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that World retains a "pure" instance of every item type + for future reference. Each Place holds its own item objects that + can be changed or removed at will. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(self.items[item_name]) + + def get_monster(self, monster_name): + ''' + Returns a copy of the monster object stored under the passed name. + This means that World retains a "pure" instance of every monster type + for future reference. Each Place holds its own monster objects that + can be changed at will (e.g. in a fight). + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(self.monsters[monster_name]) diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..56c3add --- /dev/null +++ b/src/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc index 87708fb..e273538 100644 --- a/src/__pycache__/server.cpython-34.pyc +++ b/src/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc index f29617b..9f6b508 100644 --- a/src/__pycache__/world.cpython-34.pyc +++ b/src/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/src/atlantis.py b/src/atlantis.py index ebbb075..2999593 100644 --- a/src/atlantis.py +++ b/src/atlantis.py @@ -7,7 +7,7 @@ # date: 02/05/2015 # -VERSION = (0, 0, 3) #release, major revision, minor (git) revision +VERSION = (0, 0, 5) #release, major revision, minor (git) revision import sys import os @@ -48,7 +48,7 @@ #TODO! def act_on_choice(choice): if choice == "s" or choice == "S": - print("What port do you want to start the server on?") #indicate range! + print("What port do you want to start the server on?") #TODO indicate range! server_port = input(">> ") print("What world file do you want to load?") print("(Specify an absolute or relative path)") @@ -72,7 +72,7 @@ else: print("Invalid choice!") input("Please press ENTER") - startMenu() + start_menu() def print_version(): @@ -99,9 +99,9 @@ if "--version" in sys.argv or "-v" in sys.argv: print_version() elif "--help" in sys.argv or "-h" in sys.argv: print_help() elif "--server" in sys.argv and "--world" in sys.argv: - server_port = sys.argv[sys.argv.find("--server")+1] + server_port = sys.argv[sys.argv.index("--server")+1] world_file = sys.argv[sys.argv.index("--world")+1] - Server(port, world_file) + Server(server_port, world_file) elif "--client" in sys.argv: server_address = sys.argv[sys.argv.index("--client")+1] server_ip = server_address[:server_address.find(":")] diff --git a/src/interpreter.py b/src/interpreter.py index 86cd37e..6db846e 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -12,67 +12,134 @@ from world import World -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - - -class OptionCommand(object): - ''' - This is the super class for all option commands. (A define command - includes one or more option commands.) - ''' - - def __init__(self, name, doc_string, arg): - self.name = name - self.doc_string = doc_string - self.arg = arg - - def act(self): - ''' - Carry out this option commands action with argument arg. - Has to be extended by subclasses! - ''' - raise NotImplementedError - - class DefineCommand(object): ''' This is the super class for all define commands. It should be extended for each individual command, like define-place, define-monster, etc. + + Each define command works by creating the relevant object, then fleshing + it out as more options are passed to it. Finally, the finished object is + returned. ''' def __init__(self, name, doc_string): self.name = name self.doc_string = doc_string - self.option_registry = [] + self.option_registry = dict() - def add_option(self, option_command): - self.option_registry.append(option_command) - - def execute(self): + def init_object(self, object_name): ''' - Execute this command with all its options + Initialize the object this command creates ''' - for o in self.option_registry: - o.act(arg) + raise NotImplementedError + + def return_object(self): + ''' + Return the type of the game object ('place', 'npc', 'item' or + 'monster') and the object itself + ''' + raise NotImplementedError + + def pass_option(self, option_name, option_argument): + ''' + Pass this define command one of its options and the relevant argument + ''' + self.option_registry[option_name]["function"](option_argument) + + def add_option(self, option_name, option_docstring, option_function): + ''' + Add an option for this define command. Arguments: + option_name: The name of this option as it would appear in + an atl source file + option_docstring: A description of this option + option_function: The function that this option calls + (this function should take exactly one string argument) + ''' + option_dict = dict["docstring":option_docstring, + "function":option_function] + self.option_registry[option_name] = option_dict -define_command_registry = [] +define_command_registry = dict() def register_define_command(def_com): - define_command_registry.append(def_com) + define_command_registry[def_com.name] = def_com class Parser(object): ''' The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands. + a series of DefineCommands, which are then executed ''' - def __init__(self, world_file): - self.world_file = world_file + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.load(world_file_name) + return self.world - def generate_world(self): - return World() + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + + def add_line_commands(self): + ''' + Line commands are ATL constructs that only take up one line + (unlike define commands). All line commands must be registered in + this method by adding them to the line_command_registry and setting + their key-value to the method to be called when their key appears + in an ATL source file. + ''' + self.line_command_registry["load"] = self.load + self.line_command_registry["start-place"] = self.world.set_starting_place + + def interpret_atl(self, atl_source): + ''' + This method interprets a list of atl source lines, passing them + on to the relevant define commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + line_no = line_no + 1 + if line[0] == "#": + pass #comments are ignored + elif (line == "" or line == "\n") and define_command: + # Empty lines finish off define blocks + object_type, game_object = define_command.return_object() + self.world.add_object(object_type, game_object) + define_command = None + else: + print("Line: '"+line+"'") + command = line.split()[0] + # execute a line command + if command in line_command_registry.keys(): + line_command_registry[command](line[line.find(" ")+1:]) + # start of a define block + elif command in define_command_registry.keys(): + define_command = define_command_registry[command] + object_name = line[line.find(" ")+1:] + define_command.init_object(object_name) + # parse an option command + elif line[0] == " " or line[0] == "\t": + while line[0] == " " or line[0] == "\t": + line = line[1:] + if line and define_command: + option_command = line.split()[0] + option_arg = line[line.find(" ")+1:] + define_command.pass_option(option_command, option_arg) + else: + # XXX: What should be done here? Do nothing, raise a + # syntax error, or something else? + print("Unrecognized syntax in line "+str(line_no)+"!") + diff --git a/src/place.py b/src/place.py index 8ffd706..eabd30e 100644 --- a/src/place.py +++ b/src/place.py @@ -9,7 +9,7 @@ # date: 02/05/2015 # -from parser import DefineCommand +from interpreter import DefineCommand, register_define_command class Place(object): @@ -18,13 +18,13 @@ players, monsters, items, NPCs, and links to other places. ''' - def __init__(self, name, description, + def __init__(self, name, description="", neighbours=[], monsters=[], npc=[], items=[]): ''' The constructor for a new place. Arguments: - name: the name of this place + name: the name of this place (compulsory) description: a description string neighbours: a list of place names of places bordering on this one monsters: a list of instances of monsters @@ -64,34 +64,33 @@ self.items.remove(item) -# -# FIXME: conceptually a bit messed up, especially needs work on how the argument -# gets passed to the OptionCommand -# - -class NeighbourOption(OptionCommand): - ''' - Add a new neighbour to the current place - ''' - - def __init__(self, arg, place): - OptionCommand.__init__("neighbour", - "Add the place given in the argument as a neighbour", - arg) - self.place = place - - def act(self, arg): - self.place.add_neighbour(arg) - - class DefinePlace(DefineCommand): ''' The Atlantis language construct to create a place. ''' - def __init__(self, place): + def __init__(self): DefineCommand.__init__("define-place", "Describe a new location in the game world") - self.place = place - self.add_option(NeighbourOption(self.place)) + self.add_option("description", + "Describe this place", + set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + add_neighbour) + + def init_object(self, place_name): + self.place = Place(name=place_name) + def return_object(self): + return 'place', self.place + + # This could probably be transformed into a lambda function + def add_neighbour(self, arg): + self.place.add_neighbour(arg) + + # ...same with this one + def set_description(self, arg): + self.place.set_description(arg) + +register_define_command(DefinePlace()) diff --git a/src/server.py b/src/server.py index aaecbb4..8c6b6ea 100644 --- a/src/server.py +++ b/src/server.py @@ -8,7 +8,7 @@ # date: 02/05/2015 # -from parser import Parser +from interpreter import Parser #TODO: lock file for the server! @@ -22,5 +22,12 @@ print("The server is still under construction!") self.port = port self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.generate_world() + self.world = Parser(self.world_file) + test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("Place: "+self.world.get_place(p).name) + print("Neighbours: "+self.world.get_place(p).neighbours+"\n") diff --git a/src/world.py b/src/world.py index 0c2fe32..aaacc17 100644 --- a/src/world.py +++ b/src/world.py @@ -8,6 +8,9 @@ # date: 02/05/2015 # +import copy + + class World(object): ''' The World class saves and gives access to the current state @@ -15,39 +18,86 @@ ''' def __init__(self): - self.places = [] - self.players = [] + ''' + The constructor initializes dicts consisting of name:object pairs + for all the places, players, NPCs, items and monsters available in + the world. + ''' + self.places = dict() + self.players = dict() self.starting_place = None - - def register_place(self, place): - if self.place not in places: - self.places.append(place) - else: - print("Attempted to add a place that already exists!") - + self.npc = dict() + self.items = dict() + self.monsters = dict() + def register_player(self, player): - if player not in self.players: - self.players.append(player) + if player.name not in self.players.keys(): if player.place == None: if self.starting_place: player.place = self.starting_place else: print("No starting place set!") + self.players[player.name] = player else: print("Attempted to add a player that already exists!") - def get_place(self, name): - for p in self.places: - if p.name == name: - return p + def add_object(self, object_type, game_object): + ''' + Add a game object to the world. Acts as a wrapper around add_place, etc. + object_type: 'place', 'npc', 'item' or 'monster' + game_object: the actual object + ''' + if object_type == "place": + add_place(game_object) + elif object_type == "npc": + add_npc(game_object) + elif object_type == "item": + add_item(game_object) + elif object_type == "monster": + add_monster(game_object) + + def add_place(self, place): + self.places[place.name] = place + + def add_monster(self, monster): + self.monsters[monster.name] = monster + + def add_npc(self, npc): + self.npc[npc.name] = npc + + def add_item(self, item): + self.items[item.name] = item def get_player(self, name): - for p in self.players: - if p.name == name: - return p + return self.players[name] - def set_starting_place(self, name): - for p in self.places: - if p.name == name: - self.starting_place = p + def get_place(self, name): + return self.places[name] + + def set_starting_place(self, place_name): + self.starting_place = place_name + def get_npc(self, npc_name): + return self.npc[npc_name] + + def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that World retains a "pure" instance of every item type + for future reference. Each Place holds its own item objects that + can be changed or removed at will. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(self.items[item_name]) + + def get_monster(self, monster_name): + ''' + Returns a copy of the monster object stored under the passed name. + This means that World retains a "pure" instance of every monster type + for future reference. Each Place holds its own monster objects that + can be changed at will (e.g. in a fight). + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(self.monsters[monster_name]) diff --git a/worlds/Example/test.atl b/worlds/Example/test.atl new file mode 100644 index 0000000..b855b0a --- /dev/null +++ b/worlds/Example/test.atl @@ -0,0 +1,12 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +start-place Nowhere \ No newline at end of file