diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /dev/null +++ b/python/__pycache__/define.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /dev/null +++ b/python/__pycache__/place.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /dev/null +++ b/python/__pycache__/server.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /dev/null +++ b/python/__pycache__/world.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_name]) + diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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/item.py b/src/item.py deleted file mode 100644 index aa71680..0000000 --- a/src/item.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module takes care of the internal representation of all game items. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 10/05/2015 -# - -import copy -from define import DefineCommand, register_define_command - - -class Item(object): - ''' - This is the actual Item class, which represents a game item. - ''' - - def __init__(self, name): - pass - - -class ItemCommand(DefineCommand): - ''' - The ATL construct to describe an item - ''' - - def __init__(self): - super.__init__("define-item", - "Describe a new item that players can interact with") - self.item = None - # TODO Add option commands - - def init_object(self, item_name): - self.item = None - self.item = Item(item_name) - - def return_object(self): - return self.item - -register_define_command(ItemCommand()) - -# A list of all the items that have been defined in the game world -item_registry = dict() - -def register_item(item): - item_registry[item.name] = item - -def get_item(self, item_name): - ''' - Returns a copy of the item object stored under the passed name. - This means that registry retains a "pure" instance of every item type - for future reference. - ''' - # is deepcopy the right copy method to use? if shallow copy is - # sufficient, we could just use the inbuilt dict.copy() - return copy.deepcopy(item_registry[item_name]) - diff --git a/python/__pycache__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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/item.py b/src/item.py deleted file mode 100644 index aa71680..0000000 --- a/src/item.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module takes care of the internal representation of all game items. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 10/05/2015 -# - -import copy -from define import DefineCommand, register_define_command - - -class Item(object): - ''' - This is the actual Item class, which represents a game item. - ''' - - def __init__(self, name): - pass - - -class ItemCommand(DefineCommand): - ''' - The ATL construct to describe an item - ''' - - def __init__(self): - super.__init__("define-item", - "Describe a new item that players can interact with") - self.item = None - # TODO Add option commands - - def init_object(self, item_name): - self.item = None - self.item = Item(item_name) - - def return_object(self): - return self.item - -register_define_command(ItemCommand()) - -# A list of all the items that have been defined in the game world -item_registry = dict() - -def register_item(item): - item_registry[item.name] = item - -def get_item(self, item_name): - ''' - Returns a copy of the item object stored under the passed name. - This means that registry retains a "pure" instance of every item type - for future reference. - ''' - # is deepcopy the right copy method to use? if shallow copy is - # sufficient, we could just use the inbuilt dict.copy() - return copy.deepcopy(item_registry[item_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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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/item.py b/src/item.py deleted file mode 100644 index aa71680..0000000 --- a/src/item.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module takes care of the internal representation of all game items. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 10/05/2015 -# - -import copy -from define import DefineCommand, register_define_command - - -class Item(object): - ''' - This is the actual Item class, which represents a game item. - ''' - - def __init__(self, name): - pass - - -class ItemCommand(DefineCommand): - ''' - The ATL construct to describe an item - ''' - - def __init__(self): - super.__init__("define-item", - "Describe a new item that players can interact with") - self.item = None - # TODO Add option commands - - def init_object(self, item_name): - self.item = None - self.item = Item(item_name) - - def return_object(self): - return self.item - -register_define_command(ItemCommand()) - -# A list of all the items that have been defined in the game world -item_registry = dict() - -def register_item(item): - item_registry[item.name] = item - -def get_item(self, item_name): - ''' - Returns a copy of the item object stored under the passed name. - This means that registry retains a "pure" instance of every item type - for future reference. - ''' - # is deepcopy the right copy method to use? if shallow copy is - # sufficient, we could just use the inbuilt dict.copy() - return copy.deepcopy(item_registry[item_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/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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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/item.py b/src/item.py deleted file mode 100644 index aa71680..0000000 --- a/src/item.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module takes care of the internal representation of all game items. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 10/05/2015 -# - -import copy -from define import DefineCommand, register_define_command - - -class Item(object): - ''' - This is the actual Item class, which represents a game item. - ''' - - def __init__(self, name): - pass - - -class ItemCommand(DefineCommand): - ''' - The ATL construct to describe an item - ''' - - def __init__(self): - super.__init__("define-item", - "Describe a new item that players can interact with") - self.item = None - # TODO Add option commands - - def init_object(self, item_name): - self.item = None - self.item = Item(item_name) - - def return_object(self): - return self.item - -register_define_command(ItemCommand()) - -# A list of all the items that have been defined in the game world -item_registry = dict() - -def register_item(item): - item_registry[item.name] = item - -def get_item(self, item_name): - ''' - Returns a copy of the item object stored under the passed name. - This means that registry retains a "pure" instance of every item type - for future reference. - ''' - # is deepcopy the right copy method to use? if shallow copy is - # sufficient, we could just use the inbuilt dict.copy() - return copy.deepcopy(item_registry[item_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/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__/character.cpython-34.pyc b/python/__pycache__/character.cpython-34.pyc new file mode 100644 index 0000000..20a9d5a --- /dev/null +++ b/python/__pycache__/character.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..397fe49 --- /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..3d8f05b --- /dev/null +++ b/python/__pycache__/interpreter.cpython-34.pyc Binary files differ diff --git a/python/__pycache__/item.cpython-34.pyc b/python/__pycache__/item.cpython-34.pyc new file mode 100644 index 0000000..bc4b363 --- /dev/null +++ b/python/__pycache__/item.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..bf21fc7 --- /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..3ac6f1d --- /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..1814006 --- /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..2c5802f --- /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, 8) #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..68d8ee8 --- /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/character.py b/python/character.py new file mode 100644 index 0000000..4acbf34 --- /dev/null +++ b/python/character.py @@ -0,0 +1,179 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# The character module is in charge of everything to do with the internal +# representation of a player character, including its race and class. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 02/05/2015 +# + +import item +from define import DefineCommand, register_define_command + + +class Race(object): + ''' + A class to describe the races inhabiting the game world + ''' + + def __init__(self, name, description="", + str_bonus=0, dex_bonus=0, + int_bonus=0, const_bonus=0): + ''' + Create a new race. Arguments: + name, description: the name and description of the new race + str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given + to all players of this race + ''' + self.name = name + self.description = description + self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, + 'intelligence':int_bonus, 'constitution':const_bonus} + + def set_description(self, description): + self.description = description + + def set_strength(self, str_bonus): + self.bonuses['strength'] = str_bonus + + def set_dexterity(self, dex_bonus): + self.bonuses['dexterity'] = dex_bonus + + def set_constitution(self, const_bonus): + self.bonuses['constitution'] = const_bonus + + def set_intelligence(self, int_bonus): + self.bonuses['intelligence'] = int_bonus + + +class DefineRace(DefineCommand): + ''' + The ATL construct to define a race + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-race", + "Define and describe a race of beings") + self.race = None + # FIXME: need new option methods to act as wrappers around self.race.x + # (self.race doesn't exist yet) + self.add_option("description", "A description of this race", + self.race.set_description) + self.add_option("strength", "The strength bonus that this race gets", + self.race.set_strength) + self.add_option("dexterity", "The dexterity bonus that this race gets", + self.race.set_dexterity) + self.add_option("constitution", "The constitution bonus that this race gets", + self.race.set_constitution) + self.add_option("intelligence", "The intelligence bonus that this race gets", + self.race.set_intelligence) + + def init_object(self, race_name): + self.race = None + self.race = Race(race_name) + + def return_object(self): + return self.race + +register_define_command(DefineRace()) + + +class CharacterClass(object): + ''' + A class to describe all the character classes a player can assume + ''' + + def __init__(self, name, description="", special_items=[]): + ''' + Create a new character class. Arguments: + name, description: the name and description of this character class + special_items: a list of item instances that all players of this + class carry from the start + ''' + self.name = name + self.description = description + self.items = special_items + + def set_description(self, description): + self.description = description + + def add_item(self, item_name): + new_item = item.get_item(item_name) + self.items.append(new_item) + + +class DefineClass(DefineCommand): + ''' + The ATL construct to define a character class + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-class", + "Define and describe a character class") + self.char_class = None + # FIXME: need new option methods to act as wrappers around + # self.char_class.x (self.char_class doesn't exist yet) + self.add_option("description", "Describe this character class", + self.char_class.set_description) + self.add_option("item", "An item that all members of this class carry", + self.char_class.add_item) + + def init_object(self, class_name): + self.char_class = None + self.char_class = CharacterClass(name) + + def return_object(self): + return self.char_class + +register_define_command(DefineClass()) + + +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 = [] + + def change_location(self, new_place): + self.location = new_place + + def change_health(self, difference): + self.health = self.health+difference + + def increase_attribute(self, attribute, difference): + self.attributes[attribute] = self.attributes[attribute] + difference + + def add_item(self, item): + self.items.append(item) + + # Warning: might cause bugs! + def remove_item(self, item): + self.items.remove(item) + + def add_weapon(self, weapon): + self.items.append(weapon) + + # Warning: might cause bugs! + def remove_weapon(self, weapon): + self.items.remove(weapon) + 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..8c22e73 --- /dev/null +++ b/python/define.py @@ -0,0 +1,66 @@ +#!/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 + +def get_define_command(command_name): + return define_command_registry[command_name] 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/item.py b/python/item.py new file mode 100644 index 0000000..f99092c --- /dev/null +++ b/python/item.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# +# Atlantis is a framework for creating multi-user dungeon worlds. +# This module takes care of the internal representation of all game items. +# +# Licensed under the terms of the GPLv3 +# author: Daniel Vedder +# date: 10/05/2015 +# + +import copy +from define import DefineCommand, register_define_command + + +class Item(object): + ''' + This is the actual Item class, which represents a game item. + ''' + + def __init__(self, name): + pass + + +class ItemCommand(DefineCommand): + ''' + The ATL construct to describe an item + ''' + + def __init__(self): + DefineCommand.__init__(self, "define-item", + "Describe a new item that players can interact with") + self.item = None + # TODO Add option commands + + def init_object(self, item_name): + self.item = None + self.item = Item(item_name) + + def return_object(self): + return self.item + +register_define_command(ItemCommand()) + +# A list of all the items that have been defined in the game world +item_registry = dict() + +def register_item(item): + item_registry[item.name] = item + +def get_item(self, item_name): + ''' + Returns a copy of the item object stored under the passed name. + This means that registry retains a "pure" instance of every item type + for future reference. + ''' + # is deepcopy the right copy method to use? if shallow copy is + # sufficient, we could just use the inbuilt dict.copy() + return copy.deepcopy(item_registry[item_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/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..2716b75 --- /dev/null +++ b/python/world.py @@ -0,0 +1,102 @@ +#!/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 place +from character import Race, CharacterClass, Player +from item import Item + + +class World(object): + ''' + The World class saves and gives access to the current state + of the game world. + ''' + + # Originally it was intended that the world hold a copy of every game + # object defined, and thus act as a meta-registry. However, that will + # most likely lead to circular dependencies. (For example, CharacterClass + # requires access to the item registry. If the latter was located here in + # world.py, character.py would have to import world.py, which in turn + # imports character.py...) + # Thus it seems better that each registry be moved to the module that + # deals with the relevant game object. The item registry has already been + # moved to item.py. The monster registry needs to follow suit. I am not + # yet sure what to do with the place and NPC registries. + + 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.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 == "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 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] + + + # TODO Move to monster.py + 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.py b/src/atlantis.py deleted file mode 100644 index 2c5802f..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, 8) #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/banner.txt b/src/banner.txt deleted file mode 100644 index 68d8ee8..0000000 --- a/src/banner.txt +++ /dev/null @@ -1,10 +0,0 @@ -==================================================== -|| \ / || -|| - O -MM _ MM ATLANTIS || -|| / \ ||_/.\_|| || -|| |*| |*| Lost worlds await || -|| ~~~~~| | A | |~~~~ || -|| ~~~~###########~~~ || -|| ~~~#############~~ (c) 2015 Daniel Vedder || -|| ~~~~~~~~~~~~~~~~~~ || -==================================================== diff --git a/src/character.py b/src/character.py deleted file mode 100644 index ba00075..0000000 --- a/src/character.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# The character module is in charge of everything to do with the internal -# representation of a player character, including its race and class. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 02/05/2015 -# - -import Item -from define import DefineCommand, register_define_command - - -class Race(object): - ''' - A class to describe the races inhabiting the game world - ''' - - def __init__(self, name, description="", - str_bonus=0, dex_bonus=0, - int_bonus=0, const_bonus=0): - ''' - Create a new race. Arguments: - name, description: the name and description of the new race - str_bonus, dex_bonus, int_bonus, const_bonus: attribute bonuses given - to all players of this race - ''' - self.name = name - self.description = description - self.bonuses = {'strength':str_bonus, 'dexterity':dex_bonus, - 'intelligence':int_bonus, 'constitution':const_bonus} - - def set_description(self, description): - self.description = description - - def set_strength(self, str_bonus): - self.bonuses['strength'] = str_bonus - - def set_dexterity(self, dex_bonus): - self.bonuses['dexterity'] = dex_bonus - - def set_constitution(self, const_bonus): - self.bonuses['constitution'] = const_bonus - - def set_intelligence(self, int_bonus): - self.bonuses['intelligence'] = int_bonus - - -class DefineRace(DefineCommand): - ''' - The ATL construct to define a race - ''' - - def __init__(self): - super.__init__("define-race", "Define and describe a race of beings") - self.race = None - self.add_option("description", "A description of this race", - self.race.set_description) - self.add_option("strength", "The strength bonus that this race gets", - self.race.set_strength) - self.add_option("dexterity", "The dexterity bonus that this race gets", - self.race.set_dexterity) - self.add_option("constitution", "The constitution bonus that this race gets", - self.race.set_constitution) - self.add_option("intelligence", "The intelligence bonus that this race gets", - self.race.set_intelligence) - - def init_object(self, race_name): - self.race = None - self.race = Race(race_name) - - def return_object(self): - return self.race - -register_define_command(DefineRace()) - - -class CharacterClass(object): - ''' - A class to describe all the character classes a player can assume - ''' - - def __init__(self, name, description="", special_items=[]): - ''' - Create a new character class. Arguments: - name, description: the name and description of this character class - special_items: a list of item instances that all players of this - class carry from the start - ''' - self.name = name - self.description = description - self.items = special_items - - def set_description(self, description): - self.description = description - - def add_item(self, item_name): - item = Item.get_item(item_name) - self.items.append(item) - - -class DefineClass(DefineCommand): - ''' - The ATL construct to define a character class - ''' - - def __init__(self): - super.__init__("define-class", "Define and describe a character class") - self.char_class = None - self.add_option("description", "Describe this character class", - self.char_class.set_description) - self.add_option("item", "An item that all members of this class carry", - self.char_class.add_item) - - def init_object(self, class_name): - self.char_class = None - self.char_class = CharacterClass(name) - - def return_object(self): - return self.char_class - -register_define_command(DefineClass()) - - -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 = [] - - def change_location(self, new_place): - self.location = new_place - - def change_health(self, difference): - self.health = self.health+difference - - def increase_attribute(self, attribute, difference): - self.attributes[attribute] = self.attributes[attribute] + difference - - def add_item(self, item): - self.items.append(item) - - # Warning: might cause bugs! - def remove_item(self, item): - self.items.remove(item) - - def add_weapon(self, weapon): - self.items.append(weapon) - - # Warning: might cause bugs! - def remove_weapon(self, weapon): - self.items.remove(weapon) - 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 8c22e73..0000000 --- a/src/define.py +++ /dev/null @@ -1,66 +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 - -def get_define_command(command_name): - return define_command_registry[command_name] 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/item.py b/src/item.py deleted file mode 100644 index aa71680..0000000 --- a/src/item.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python3 -# -# Atlantis is a framework for creating multi-user dungeon worlds. -# This module takes care of the internal representation of all game items. -# -# Licensed under the terms of the GPLv3 -# author: Daniel Vedder -# date: 10/05/2015 -# - -import copy -from define import DefineCommand, register_define_command - - -class Item(object): - ''' - This is the actual Item class, which represents a game item. - ''' - - def __init__(self, name): - pass - - -class ItemCommand(DefineCommand): - ''' - The ATL construct to describe an item - ''' - - def __init__(self): - super.__init__("define-item", - "Describe a new item that players can interact with") - self.item = None - # TODO Add option commands - - def init_object(self, item_name): - self.item = None - self.item = Item(item_name) - - def return_object(self): - return self.item - -register_define_command(ItemCommand()) - -# A list of all the items that have been defined in the game world -item_registry = dict() - -def register_item(item): - item_registry[item.name] = item - -def get_item(self, item_name): - ''' - Returns a copy of the item object stored under the passed name. - This means that registry retains a "pure" instance of every item type - for future reference. - ''' - # is deepcopy the right copy method to use? if shallow copy is - # sufficient, we could just use the inbuilt dict.copy() - return copy.deepcopy(item_registry[item_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/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 a603c26..0000000 --- a/src/world.py +++ /dev/null @@ -1,101 +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 place -from character import Race, CharacterClass, Player - - -class World(object): - ''' - The World class saves and gives access to the current state - of the game world. - ''' - - # Originally it was intended that the world hold a copy of every game - # object defined, and thus act as a meta-registry. However, that will - # most likely lead to circular dependencies. (For example, CharacterClass - # requires access to the item registry. If the latter was located here in - # world.py, character.py would have to import world.py, which in turn - # imports character.py...) - # Thus it seems better that each registry be moved to the module that - # deals with the relevant game object. The item registry has already been - # moved to item.py. The monster registry needs to follow suit. I am not - # yet sure what to do with the place and NPC registries. - - 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.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 == "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 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] - - - # TODO Move to monster.py - 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])