diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/src/place.py b/src/place.py deleted file mode 100644 index 5e89425..0000000 --- a/src/place.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module contains the code for a place (a delimited location -# in the world). -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from define import DefineCommand, register_define_command - - -class Place(object): - ''' - A Place is one discrete location in the game world, which can contain - players, monsters, items, NPCs, and links to other places. - ''' - - def __init__(self, name): - ''' - The constructor for a new place. - Instance variables: - 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 - npc: a list of instances of NPCs - items: a list of instances of items - ''' - self.name = name - self.description = "" - self.neighbours = [] - self.monsters = [] - self.npc = [] - self.items = [] - - def set_description(self, description): - self.description = description - - def add_neighbour(self, place_name): - if place_name not in self.neighbours: - self.neighbours.append(place_name) - - def add_monster(self, monster): - self.monsters.append(monster) - - def add_npc(self, npc): - self.npc.append(npc) - - def add_item(self, item): - self.items.append(item) - - def remove_neighbour(self, place_name): - self.neighbours.remove(place_name) - - # XXX The following methods might cause problems if one attempts to - # remove just one instance of a list that contains several similar - # instances. - def remove_monster(self, monster): - self.monsters.remove(monster) - - def remove_npc(self, npc): - self.npc.remove(npc) - - def remove_item(self, item): - self.items.remove(item) - - -class DefinePlace(DefineCommand): - ''' - The Atlantis language construct to create a place. - ''' - - def __init__(self): - DefineCommand.__init__(self, "define-place", - "Describe a new location in the game world") - self.add_option("description", - "Describe this place", - self.set_description) - self.add_option("neighbour", - "Add a neighbouring place to this place", - self.add_neighbour) - - def init_object(self, place_name): - self.place = None - 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/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/src/place.py b/src/place.py deleted file mode 100644 index 5e89425..0000000 --- a/src/place.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module contains the code for a place (a delimited location -# in the world). -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from define import DefineCommand, register_define_command - - -class Place(object): - ''' - A Place is one discrete location in the game world, which can contain - players, monsters, items, NPCs, and links to other places. - ''' - - def __init__(self, name): - ''' - The constructor for a new place. - Instance variables: - 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 - npc: a list of instances of NPCs - items: a list of instances of items - ''' - self.name = name - self.description = "" - self.neighbours = [] - self.monsters = [] - self.npc = [] - self.items = [] - - def set_description(self, description): - self.description = description - - def add_neighbour(self, place_name): - if place_name not in self.neighbours: - self.neighbours.append(place_name) - - def add_monster(self, monster): - self.monsters.append(monster) - - def add_npc(self, npc): - self.npc.append(npc) - - def add_item(self, item): - self.items.append(item) - - def remove_neighbour(self, place_name): - self.neighbours.remove(place_name) - - # XXX The following methods might cause problems if one attempts to - # remove just one instance of a list that contains several similar - # instances. - def remove_monster(self, monster): - self.monsters.remove(monster) - - def remove_npc(self, npc): - self.npc.remove(npc) - - def remove_item(self, item): - self.items.remove(item) - - -class DefinePlace(DefineCommand): - ''' - The Atlantis language construct to create a place. - ''' - - def __init__(self): - DefineCommand.__init__(self, "define-place", - "Describe a new location in the game world") - self.add_option("description", - "Describe this place", - self.set_description) - self.add_option("neighbour", - "Add a neighbouring place to this place", - self.add_neighbour) - - def init_object(self, place_name): - self.place = None - 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/player.py b/src/player.py deleted file mode 100644 index 4416ec8..0000000 --- a/src/player.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The player module is in charge of everything to do with the internal -# representation of a player character. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Player(object): - ''' - The Player class represents a game character. - ''' - - def __init__(self, name, start_health, - character_race, character_class, - start_attributes): - ''' - Create a player. Arguments: - name - The name of the character - start_health - The initial max HP - character_race - a race instance the character belongs to - character_class - a class instance that the character belongs to - start_attributes - A dict containing the player attributes - ('strength', 'constitution', 'dexterity', 'intelligence') - ''' - self.name = name - self.location = "" - self.health = start_health - self.attributes = start_attributes - self.weapons = [] - self.items = [] - diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/src/place.py b/src/place.py deleted file mode 100644 index 5e89425..0000000 --- a/src/place.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module contains the code for a place (a delimited location -# in the world). -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from define import DefineCommand, register_define_command - - -class Place(object): - ''' - A Place is one discrete location in the game world, which can contain - players, monsters, items, NPCs, and links to other places. - ''' - - def __init__(self, name): - ''' - The constructor for a new place. - Instance variables: - 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 - npc: a list of instances of NPCs - items: a list of instances of items - ''' - self.name = name - self.description = "" - self.neighbours = [] - self.monsters = [] - self.npc = [] - self.items = [] - - def set_description(self, description): - self.description = description - - def add_neighbour(self, place_name): - if place_name not in self.neighbours: - self.neighbours.append(place_name) - - def add_monster(self, monster): - self.monsters.append(monster) - - def add_npc(self, npc): - self.npc.append(npc) - - def add_item(self, item): - self.items.append(item) - - def remove_neighbour(self, place_name): - self.neighbours.remove(place_name) - - # XXX The following methods might cause problems if one attempts to - # remove just one instance of a list that contains several similar - # instances. - def remove_monster(self, monster): - self.monsters.remove(monster) - - def remove_npc(self, npc): - self.npc.remove(npc) - - def remove_item(self, item): - self.items.remove(item) - - -class DefinePlace(DefineCommand): - ''' - The Atlantis language construct to create a place. - ''' - - def __init__(self): - DefineCommand.__init__(self, "define-place", - "Describe a new location in the game world") - self.add_option("description", - "Describe this place", - self.set_description) - self.add_option("neighbour", - "Add a neighbouring place to this place", - self.add_neighbour) - - def init_object(self, place_name): - self.place = None - 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/player.py b/src/player.py deleted file mode 100644 index 4416ec8..0000000 --- a/src/player.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The player module is in charge of everything to do with the internal -# representation of a player character. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Player(object): - ''' - The Player class represents a game character. - ''' - - def __init__(self, name, start_health, - character_race, character_class, - start_attributes): - ''' - Create a player. Arguments: - name - The name of the character - start_health - The initial max HP - character_race - a race instance the character belongs to - character_class - a class instance that the character belongs to - start_attributes - A dict containing the player attributes - ('strength', 'constitution', 'dexterity', 'intelligence') - ''' - self.name = name - self.location = "" - self.health = start_health - self.attributes = start_attributes - self.weapons = [] - self.items = [] - diff --git a/src/server.py b/src/server.py deleted file mode 100644 index 2c1ce87..0000000 --- a/src/server.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the server module which is in ultimately in charge of all game logic. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from interpreter import Parser -from world import World - -#TODO: lock file for the server! - -class Server(object): - ''' - This is the master server class in charge of setting up everything - necessary for a game. - ''' - - def __init__(self, port, world_file): - print("The server is still under construction!") - self.port = port - self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.load(self.world_file) - self.test_parser() - - def test_parser(self): - print("World loaded. Details:") - places = self.world.places.keys() - for p in places: - print("\nPlace: "+self.world.get_place(p).name) - print("Description: "+self.world.get_place(p).description) - print("Neighbours: "+str(self.world.get_place(p).neighbours)) - print("Starting place: "+self.world.starting_place) diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/src/place.py b/src/place.py deleted file mode 100644 index 5e89425..0000000 --- a/src/place.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module contains the code for a place (a delimited location -# in the world). -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from define import DefineCommand, register_define_command - - -class Place(object): - ''' - A Place is one discrete location in the game world, which can contain - players, monsters, items, NPCs, and links to other places. - ''' - - def __init__(self, name): - ''' - The constructor for a new place. - Instance variables: - 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 - npc: a list of instances of NPCs - items: a list of instances of items - ''' - self.name = name - self.description = "" - self.neighbours = [] - self.monsters = [] - self.npc = [] - self.items = [] - - def set_description(self, description): - self.description = description - - def add_neighbour(self, place_name): - if place_name not in self.neighbours: - self.neighbours.append(place_name) - - def add_monster(self, monster): - self.monsters.append(monster) - - def add_npc(self, npc): - self.npc.append(npc) - - def add_item(self, item): - self.items.append(item) - - def remove_neighbour(self, place_name): - self.neighbours.remove(place_name) - - # XXX The following methods might cause problems if one attempts to - # remove just one instance of a list that contains several similar - # instances. - def remove_monster(self, monster): - self.monsters.remove(monster) - - def remove_npc(self, npc): - self.npc.remove(npc) - - def remove_item(self, item): - self.items.remove(item) - - -class DefinePlace(DefineCommand): - ''' - The Atlantis language construct to create a place. - ''' - - def __init__(self): - DefineCommand.__init__(self, "define-place", - "Describe a new location in the game world") - self.add_option("description", - "Describe this place", - self.set_description) - self.add_option("neighbour", - "Add a neighbouring place to this place", - self.add_neighbour) - - def init_object(self, place_name): - self.place = None - 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/player.py b/src/player.py deleted file mode 100644 index 4416ec8..0000000 --- a/src/player.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The player module is in charge of everything to do with the internal -# representation of a player character. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Player(object): - ''' - The Player class represents a game character. - ''' - - def __init__(self, name, start_health, - character_race, character_class, - start_attributes): - ''' - Create a player. Arguments: - name - The name of the character - start_health - The initial max HP - character_race - a race instance the character belongs to - character_class - a class instance that the character belongs to - start_attributes - A dict containing the player attributes - ('strength', 'constitution', 'dexterity', 'intelligence') - ''' - self.name = name - self.location = "" - self.health = start_health - self.attributes = start_attributes - self.weapons = [] - self.items = [] - diff --git a/src/server.py b/src/server.py deleted file mode 100644 index 2c1ce87..0000000 --- a/src/server.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the server module which is in ultimately in charge of all game logic. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from interpreter import Parser -from world import World - -#TODO: lock file for the server! - -class Server(object): - ''' - This is the master server class in charge of setting up everything - necessary for a game. - ''' - - def __init__(self, port, world_file): - print("The server is still under construction!") - self.port = port - self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.load(self.world_file) - self.test_parser() - - def test_parser(self): - print("World loaded. Details:") - places = self.world.places.keys() - for p in places: - print("\nPlace: "+self.world.get_place(p).name) - print("Description: "+self.world.get_place(p).description) - print("Neighbours: "+str(self.world.get_place(p).neighbours)) - print("Starting place: "+self.world.starting_place) diff --git a/src/ui.py b/src/ui.py deleted file mode 100644 index b78baae..0000000 --- a/src/ui.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the (text) UI module - all commandline IO should pass through here. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - - -# -# DO NOT USE YET !!! -# - -class IO(object): - ''' - A wrapper to stdout, stderr and stdin. - Intendend for development work only! - ''' - - def __init__(self): - pass - - def error(self, error_text): - pass diff --git a/python/__pycache__/client.cpython-34.pyc b/python/__pycache__/client.cpython-34.pyc new file mode 100644 index 0000000..077e656 --- /dev/null +++ b/python/__pycache__/client.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/define.cpython-34.pyc b/python/__pycache__/define.cpython-34.pyc new file mode 100644 index 0000000..35ab663 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-32.pyc b/python/__pycache__/interpreter.cpython-32.pyc new file mode 100644 index 0000000..2d36d9f --- /dev/null +++ b/python/__pycache__/interpreter.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/interpreter.cpython-34.pyc b/python/__pycache__/interpreter.cpython-34.pyc new file mode 100644 index 0000000..5f79b87 --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/parser.cpython-34.pyc b/python/__pycache__/parser.cpython-34.pyc new file mode 100644 index 0000000..ffeaf68 --- /dev/null +++ b/python/__pycache__/parser.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-32.pyc b/python/__pycache__/place.cpython-32.pyc new file mode 100644 index 0000000..721a318 --- /dev/null +++ b/python/__pycache__/place.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/place.cpython-34.pyc b/python/__pycache__/place.cpython-34.pyc new file mode 100644 index 0000000..f8f3cd6 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/server.cpython-34.pyc b/python/__pycache__/server.cpython-34.pyc new file mode 100644 index 0000000..72cdf70 --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-32.pyc b/python/__pycache__/world.cpython-32.pyc new file mode 100644 index 0000000..4276447 --- /dev/null +++ b/python/__pycache__/world.cpython-32.pyc Binary files differ diff --git a/python/__pycache__/world.cpython-34.pyc b/python/__pycache__/world.cpython-34.pyc new file mode 100644 index 0000000..7e57b15 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/atlantis.py b/python/atlantis.py new file mode 100644 index 0000000..2a8490e --- /dev/null +++ b/python/atlantis.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +VERSION = (0, 0, 6) #release, major revision, minor (git) revision + +import sys +import os +from server import Server +from client import Client + + +global clear +if "linux" in sys.platform: + clear = "clear" +elif "win" in sys.platform: + clear = "cls" + + +def start_menu(): + ''' + Show the start menu and process the player's input + ''' + global clear + os.system(clear) + try: #Print the Atlantis logo + fn = os.path.join(os.path.dirname(__file__), "banner.txt") + img_file = open(fn, "r") + logo = img_file.read() + img_file.close() + print(logo) + except IOError: + print("Could not find logo!") + print("Welcome! What do you want to do?") + print(" -> (S)tart a server") + print(" -> (J)oin game") + print(" -> (A)bout") + print(" -> (E)xit") + print() + choice = input("Please choose an option: ") + act_on_choice(choice) + +#TODO! +def act_on_choice(choice): + if choice == "s" or choice == "S": + 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)") + world_file = input(">> ") + Server(server_port, world_file) + elif choice == "j" or choice == "J": + print("What server do you want to connect to?") + print("Format: :") + server_address = input(">> ") + server_ip = server_address[:server_address.find(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + elif choice == "a" or choice == "A": + print_version() + print("(c) 2015 Daniel Vedder") + input("\nPlease press ENTER") + start_menu() + elif choice == "e" or choice == "E": + print("Goodbye!") + exit() + else: + print("Invalid choice!") + input("Please press ENTER") + start_menu() + + +def print_version(): + version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) + print("Atlantis "+version_string) + print("Licensed under the terms of the GNU General Public License v3.") + +def print_help(): + help_text = """ +Usage: atlantis [options] + + --help -h Print this help text and exit + --version -v Print the version number and exit + --server Start an atlantis server on (requires --world) + --world Use as the world file (requires --server) + --client : Connect as a client to the server at : + +If no arguments are passed, atlantis starts an interactive interface. +""" + print_version() + print(help_text) + +def main(): + 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.index("--server")+1] + world_file = sys.argv[sys.argv.index("--world")+1] + 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(":")] + server_port = server_address[server_address.find(":")+1:] + Client(server_ip, server_port) + else: + start_menu() + +if __name__ == "__main__": + main() diff --git a/python/banner.txt b/python/banner.txt new file mode 100644 index 0000000..f845fc6 --- /dev/null +++ b/python/banner.txt @@ -0,0 +1,10 @@ +==================================================== +|| / || +|| O - MM _ MM ATLANTIS || +|| | \ ||_/.\_|| || +|| |*| |*| Lost worlds await || +|| ~~~~~| | A | |~~~~ || +|| ~~~~###########~~~ || +|| ~~~#############~~ (c) 2015 Daniel Vedder || +|| ~~~~~~~~~~~~~~~~~~ || +==================================================== diff --git a/python/client.py b/python/client.py new file mode 100644 index 0000000..93a18e3 --- /dev/null +++ b/python/client.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the client module that the player interacts with. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Client(object): + ''' + The client is the interface between the player and the server. + ''' + + def __init__(self, server_ip=None, server_port=None): + if not (server_ip and server_port): + print("What server IP do you want to connect to?") + server_ip = input(">> ") + print("What port are you using?") + server_port = input(">> ") + print("The client would connect to "+server_ip+":"+server_port) diff --git a/python/define.py b/python/define.py new file mode 100644 index 0000000..7111301 --- /dev/null +++ b/python/define.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module houses the code for the define blocks of the Atlantis +# ATL language. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 04/05/2015 +# + + +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 = dict() + + def init_object(self, object_name): + ''' + Initialize the object this command creates + ''' + 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 = dict() + +def register_define_command(def_com): + define_command_registry[def_com.name] = def_com + diff --git a/python/interpreter.py b/python/interpreter.py new file mode 100644 index 0000000..69d7fc1 --- /dev/null +++ b/python/interpreter.py @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module reads in a world file and parses it, using the DefineCommands +# defined in other modules. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import os +from world import World +from define import DefineCommand, define_command_registry + + +class Parser(object): + ''' + The actual parser class. It reads in a world file and transforms it into + a series of DefineCommands, which are then executed + ''' + + def __init__(self, world_file_name): + self.line_command_registry = dict() + self.world = World() + self.add_line_commands() + self.world_file_name = world_file_name + + def load(self, world_file_name): + ''' + Load a world file and pass it on to the interpreter + ''' + try: + world_file = open(self.world_file_name, 'r') + atl_text = world_file.readlines() + world_file.close() + print("Loaded "+world_file_name) + except IOError: + print("Failed to load world file '"+world_file_name+"'!") + self.interpret_atl(atl_text) + return self.world + + 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.secondary_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 commands. + ''' + define_command = None # The define command currently being executed + line_no = 0 + for line in atl_source: + # TODO allow for linebreaks + # while line[-2] == "\\": + # line = line + atl_source[line_no+1] + # line_no = line_no+1 + line_no = line_no + 1 + if line[-1] != "\n": + # make sure each line ends with a line break, otherwise we run + # into trouble with the last line + line = line+"\n" + if len(line) < 2 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 + elif len(line) < 2 or line[0] == "#": + pass #comments and empty lines are ignored + else: + command = line.split()[0] + # execute a line command + if command in self.line_command_registry.keys(): + self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") + + def secondary_load(self, atl_file_name): + ''' + --- DO NOT USE --- + + The function that prepares everything before loading in another ATL file + when the 'load' command is called. + + Currently leads to an endless recursion loop. + ''' + world_directory = os.path.split(self.world_file_name)[0] + load_file_name = os.path.join(world_directory, atl_file_name) + self.load(load_file_name) diff --git a/python/place.py b/python/place.py new file mode 100644 index 0000000..5e89425 --- /dev/null +++ b/python/place.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module contains the code for a place (a delimited location +# in the world). +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from define import DefineCommand, register_define_command + + +class Place(object): + ''' + A Place is one discrete location in the game world, which can contain + players, monsters, items, NPCs, and links to other places. + ''' + + def __init__(self, name): + ''' + The constructor for a new place. + Instance variables: + 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 + npc: a list of instances of NPCs + items: a list of instances of items + ''' + self.name = name + self.description = "" + self.neighbours = [] + self.monsters = [] + self.npc = [] + self.items = [] + + def set_description(self, description): + self.description = description + + def add_neighbour(self, place_name): + if place_name not in self.neighbours: + self.neighbours.append(place_name) + + def add_monster(self, monster): + self.monsters.append(monster) + + def add_npc(self, npc): + self.npc.append(npc) + + def add_item(self, item): + self.items.append(item) + + def remove_neighbour(self, place_name): + self.neighbours.remove(place_name) + + # XXX The following methods might cause problems if one attempts to + # remove just one instance of a list that contains several similar + # instances. + def remove_monster(self, monster): + self.monsters.remove(monster) + + def remove_npc(self, npc): + self.npc.remove(npc) + + def remove_item(self, item): + self.items.remove(item) + + +class DefinePlace(DefineCommand): + ''' + The Atlantis language construct to create a place. + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-place", + "Describe a new location in the game world") + self.add_option("description", + "Describe this place", + self.set_description) + self.add_option("neighbour", + "Add a neighbouring place to this place", + self.add_neighbour) + + def init_object(self, place_name): + self.place = None + 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/python/player.py b/python/player.py new file mode 100644 index 0000000..4416ec8 --- /dev/null +++ b/python/player.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The player module is in charge of everything to do with the internal +# representation of a player character. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +class Player(object): + ''' + The Player class represents a game character. + ''' + + def __init__(self, name, start_health, + character_race, character_class, + start_attributes): + ''' + Create a player. Arguments: + name - The name of the character + start_health - The initial max HP + character_race - a race instance the character belongs to + character_class - a class instance that the character belongs to + start_attributes - A dict containing the player attributes + ('strength', 'constitution', 'dexterity', 'intelligence') + ''' + self.name = name + self.location = "" + self.health = start_health + self.attributes = start_attributes + self.weapons = [] + self.items = [] + diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..2c1ce87 --- /dev/null +++ b/python/server.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the server module which is in ultimately in charge of all game logic. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +from interpreter import Parser +from world import World + +#TODO: lock file for the server! + +class Server(object): + ''' + This is the master server class in charge of setting up everything + necessary for a game. + ''' + + def __init__(self, port, world_file): + print("The server is still under construction!") + self.port = port + self.world_file = world_file + parser = Parser(self.world_file) + self.world = parser.load(self.world_file) + self.test_parser() + + def test_parser(self): + print("World loaded. Details:") + places = self.world.places.keys() + for p in places: + print("\nPlace: "+self.world.get_place(p).name) + print("Description: "+self.world.get_place(p).description) + print("Neighbours: "+str(self.world.get_place(p).neighbours)) + print("Starting place: "+self.world.starting_place) diff --git a/python/ui.py b/python/ui.py new file mode 100644 index 0000000..b78baae --- /dev/null +++ b/python/ui.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This is the (text) UI module - all commandline IO should pass through here. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + + +# +# DO NOT USE YET !!! +# + +class IO(object): + ''' + A wrapper to stdout, stderr and stdin. + Intendend for development work only! + ''' + + def __init__(self): + pass + + def error(self, error_text): + pass diff --git a/python/world.py b/python/world.py new file mode 100644 index 0000000..15263ac --- /dev/null +++ b/python/world.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The world module saves the current state of the game world. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import copy +#import player +import place + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + def __init__(self): + ''' + 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 + self.npc = dict() + self.items = dict() + self.monsters = dict() + + def register_player(self, 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 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": + self.add_place(game_object) + elif object_type == "npc": + self.add_npc(game_object) + elif object_type == "item": + self.add_item(game_object) + elif object_type == "monster": + self.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): + return self.players[name] + + 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__/client.cpython-34.pyc b/src/__pycache__/client.cpython-34.pyc deleted file mode 100644 index 077e656..0000000 --- a/src/__pycache__/client.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/define.cpython-34.pyc b/src/__pycache__/define.cpython-34.pyc deleted file mode 100644 index 35ab663..0000000 --- a/src/__pycache__/define.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-32.pyc b/src/__pycache__/interpreter.cpython-32.pyc deleted file mode 100644 index 2d36d9f..0000000 --- a/src/__pycache__/interpreter.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/interpreter.cpython-34.pyc b/src/__pycache__/interpreter.cpython-34.pyc deleted file mode 100644 index 5f79b87..0000000 --- a/src/__pycache__/interpreter.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/parser.cpython-34.pyc b/src/__pycache__/parser.cpython-34.pyc deleted file mode 100644 index ffeaf68..0000000 --- a/src/__pycache__/parser.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-32.pyc b/src/__pycache__/place.cpython-32.pyc deleted file mode 100644 index 721a318..0000000 --- a/src/__pycache__/place.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/place.cpython-34.pyc b/src/__pycache__/place.cpython-34.pyc deleted file mode 100644 index f8f3cd6..0000000 --- a/src/__pycache__/place.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/server.cpython-34.pyc b/src/__pycache__/server.cpython-34.pyc deleted file mode 100644 index 72cdf70..0000000 --- a/src/__pycache__/server.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-32.pyc b/src/__pycache__/world.cpython-32.pyc deleted file mode 100644 index 4276447..0000000 --- a/src/__pycache__/world.cpython-32.pyc +++ /dev/null Binary files differ diff --git a/src/__pycache__/world.cpython-34.pyc b/src/__pycache__/world.cpython-34.pyc deleted file mode 100644 index 7e57b15..0000000 --- a/src/__pycache__/world.cpython-34.pyc +++ /dev/null Binary files differ diff --git a/src/atlantis.lisp b/src/atlantis.lisp new file mode 100644 index 0000000..dd475fe --- /dev/null +++ b/src/atlantis.lisp @@ -0,0 +1,14 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; Licensed under the terms of the GPLv3 +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(defun start-menu () + "Show the start menu and take a choice from the user" + ) + +(start-menu) diff --git a/src/atlantis.py b/src/atlantis.py deleted file mode 100644 index 2a8490e..0000000 --- a/src/atlantis.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -VERSION = (0, 0, 6) #release, major revision, minor (git) revision - -import sys -import os -from server import Server -from client import Client - - -global clear -if "linux" in sys.platform: - clear = "clear" -elif "win" in sys.platform: - clear = "cls" - - -def start_menu(): - ''' - Show the start menu and process the player's input - ''' - global clear - os.system(clear) - try: #Print the Atlantis logo - fn = os.path.join(os.path.dirname(__file__), "banner.txt") - img_file = open(fn, "r") - logo = img_file.read() - img_file.close() - print(logo) - except IOError: - print("Could not find logo!") - print("Welcome! What do you want to do?") - print(" -> (S)tart a server") - print(" -> (J)oin game") - print(" -> (A)bout") - print(" -> (E)xit") - print() - choice = input("Please choose an option: ") - act_on_choice(choice) - -#TODO! -def act_on_choice(choice): - if choice == "s" or choice == "S": - 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)") - world_file = input(">> ") - Server(server_port, world_file) - elif choice == "j" or choice == "J": - print("What server do you want to connect to?") - print("Format: :") - server_address = input(">> ") - server_ip = server_address[:server_address.find(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - elif choice == "a" or choice == "A": - print_version() - print("(c) 2015 Daniel Vedder") - input("\nPlease press ENTER") - start_menu() - elif choice == "e" or choice == "E": - print("Goodbye!") - exit() - else: - print("Invalid choice!") - input("Please press ENTER") - start_menu() - - -def print_version(): - version_string = str(VERSION[0])+"."+str(VERSION[1])+"."+str(VERSION[2]) - print("Atlantis "+version_string) - print("Licensed under the terms of the GNU General Public License v3.") - -def print_help(): - help_text = """ -Usage: atlantis [options] - - --help -h Print this help text and exit - --version -v Print the version number and exit - --server Start an atlantis server on (requires --world) - --world Use as the world file (requires --server) - --client : Connect as a client to the server at : - -If no arguments are passed, atlantis starts an interactive interface. -""" - print_version() - print(help_text) - -def main(): - 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.index("--server")+1] - world_file = sys.argv[sys.argv.index("--world")+1] - 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(":")] - server_port = server_address[server_address.find(":")+1:] - Client(server_ip, server_port) - else: - start_menu() - -if __name__ == "__main__": - main() diff --git a/src/client.py b/src/client.py deleted file mode 100644 index 93a18e3..0000000 --- a/src/client.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the client module that the player interacts with. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Client(object): - ''' - The client is the interface between the player and the server. - ''' - - def __init__(self, server_ip=None, server_port=None): - if not (server_ip and server_port): - print("What server IP do you want to connect to?") - server_ip = input(">> ") - print("What port are you using?") - server_port = input(">> ") - print("The client would connect to "+server_ip+":"+server_port) diff --git a/src/define.py b/src/define.py deleted file mode 100644 index 7111301..0000000 --- a/src/define.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module houses the code for the define blocks of the Atlantis -# ATL language. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 04/05/2015 -# - - -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 = dict() - - def init_object(self, object_name): - ''' - Initialize the object this command creates - ''' - 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 = dict() - -def register_define_command(def_com): - define_command_registry[def_com.name] = def_com - diff --git a/src/interpreter.py b/src/interpreter.py deleted file mode 100644 index 69d7fc1..0000000 --- a/src/interpreter.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module reads in a world file and parses it, using the DefineCommands -# defined in other modules. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import os -from world import World -from define import DefineCommand, define_command_registry - - -class Parser(object): - ''' - The actual parser class. It reads in a world file and transforms it into - a series of DefineCommands, which are then executed - ''' - - def __init__(self, world_file_name): - self.line_command_registry = dict() - self.world = World() - self.add_line_commands() - self.world_file_name = world_file_name - - def load(self, world_file_name): - ''' - Load a world file and pass it on to the interpreter - ''' - try: - world_file = open(self.world_file_name, 'r') - atl_text = world_file.readlines() - world_file.close() - print("Loaded "+world_file_name) - except IOError: - print("Failed to load world file '"+world_file_name+"'!") - self.interpret_atl(atl_text) - return self.world - - 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.secondary_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 commands. - ''' - define_command = None # The define command currently being executed - line_no = 0 - for line in atl_source: - # TODO allow for linebreaks - # while line[-2] == "\\": - # line = line + atl_source[line_no+1] - # line_no = line_no+1 - line_no = line_no + 1 - if line[-1] != "\n": - # make sure each line ends with a line break, otherwise we run - # into trouble with the last line - line = line+"\n" - if len(line) < 2 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 - elif len(line) < 2 or line[0] == "#": - pass #comments and empty lines are ignored - else: - command = line.split()[0] - # execute a line command - if command in self.line_command_registry.keys(): - self.line_command_registry[command](line[line.find(" ")+1:-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:-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:-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)+"!") - - def secondary_load(self, atl_file_name): - ''' - --- DO NOT USE --- - - The function that prepares everything before loading in another ATL file - when the 'load' command is called. - - Currently leads to an endless recursion loop. - ''' - world_directory = os.path.split(self.world_file_name)[0] - load_file_name = os.path.join(world_directory, atl_file_name) - self.load(load_file_name) diff --git a/src/place.py b/src/place.py deleted file mode 100644 index 5e89425..0000000 --- a/src/place.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module contains the code for a place (a delimited location -# in the world). -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from define import DefineCommand, register_define_command - - -class Place(object): - ''' - A Place is one discrete location in the game world, which can contain - players, monsters, items, NPCs, and links to other places. - ''' - - def __init__(self, name): - ''' - The constructor for a new place. - Instance variables: - 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 - npc: a list of instances of NPCs - items: a list of instances of items - ''' - self.name = name - self.description = "" - self.neighbours = [] - self.monsters = [] - self.npc = [] - self.items = [] - - def set_description(self, description): - self.description = description - - def add_neighbour(self, place_name): - if place_name not in self.neighbours: - self.neighbours.append(place_name) - - def add_monster(self, monster): - self.monsters.append(monster) - - def add_npc(self, npc): - self.npc.append(npc) - - def add_item(self, item): - self.items.append(item) - - def remove_neighbour(self, place_name): - self.neighbours.remove(place_name) - - # XXX The following methods might cause problems if one attempts to - # remove just one instance of a list that contains several similar - # instances. - def remove_monster(self, monster): - self.monsters.remove(monster) - - def remove_npc(self, npc): - self.npc.remove(npc) - - def remove_item(self, item): - self.items.remove(item) - - -class DefinePlace(DefineCommand): - ''' - The Atlantis language construct to create a place. - ''' - - def __init__(self): - DefineCommand.__init__(self, "define-place", - "Describe a new location in the game world") - self.add_option("description", - "Describe this place", - self.set_description) - self.add_option("neighbour", - "Add a neighbouring place to this place", - self.add_neighbour) - - def init_object(self, place_name): - self.place = None - 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/player.py b/src/player.py deleted file mode 100644 index 4416ec8..0000000 --- a/src/player.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The player module is in charge of everything to do with the internal -# representation of a player character. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -class Player(object): - ''' - The Player class represents a game character. - ''' - - def __init__(self, name, start_health, - character_race, character_class, - start_attributes): - ''' - Create a player. Arguments: - name - The name of the character - start_health - The initial max HP - character_race - a race instance the character belongs to - character_class - a class instance that the character belongs to - start_attributes - A dict containing the player attributes - ('strength', 'constitution', 'dexterity', 'intelligence') - ''' - self.name = name - self.location = "" - self.health = start_health - self.attributes = start_attributes - self.weapons = [] - self.items = [] - diff --git a/src/server.py b/src/server.py deleted file mode 100644 index 2c1ce87..0000000 --- a/src/server.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the server module which is in ultimately in charge of all game logic. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -from interpreter import Parser -from world import World - -#TODO: lock file for the server! - -class Server(object): - ''' - This is the master server class in charge of setting up everything - necessary for a game. - ''' - - def __init__(self, port, world_file): - print("The server is still under construction!") - self.port = port - self.world_file = world_file - parser = Parser(self.world_file) - self.world = parser.load(self.world_file) - self.test_parser() - - def test_parser(self): - print("World loaded. Details:") - places = self.world.places.keys() - for p in places: - print("\nPlace: "+self.world.get_place(p).name) - print("Description: "+self.world.get_place(p).description) - print("Neighbours: "+str(self.world.get_place(p).neighbours)) - print("Starting place: "+self.world.starting_place) diff --git a/src/ui.py b/src/ui.py deleted file mode 100644 index b78baae..0000000 --- a/src/ui.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This is the (text) UI module - all commandline IO should pass through here. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - - -# -# DO NOT USE YET !!! -# - -class IO(object): - ''' - A wrapper to stdout, stderr and stdin. - Intendend for development work only! - ''' - - def __init__(self): - pass - - def error(self, error_text): - pass diff --git a/src/world.py b/src/world.py deleted file mode 100644 index 15263ac..0000000 --- a/src/world.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The world module saves the current state of the game world. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import copy -#import player -import place - - -class World(object): - ''' - The World class saves and gives access to the current state - of the game world. - ''' - - def __init__(self): - ''' - 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 - self.npc = dict() - self.items = dict() - self.monsters = dict() - - def register_player(self, 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 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": - self.add_place(game_object) - elif object_type == "npc": - self.add_npc(game_object) - elif object_type == "item": - self.add_item(game_object) - elif object_type == "monster": - self.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): - return self.players[name] - - 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])