diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/lisp/util.lisp b/lisp/util.lisp index 062834a..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -10,6 +10,23 @@ ;;; +;;; MACROS + +(defmacro let-gensyms (syms &body body) + "Gratefully copied from Paul Graham's 'On Lisp'..." + ;; I had to rename it from with-gensyms due to a naming conflict + `(let ,(mapcar #'(lambda (s) + `(,s (gensym))) + syms) + ,@body)) + +(defmacro magic (var) + "Execute typed-in Lisp code" + (let-gensyms (expr) + `(when (equalp ,var 'magic) + (progn (simple-input ,expr "[spell]>") + (eval ,expr))))) + ; potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" @@ -24,14 +41,16 @@ `(progn (format t "~&>>> ") (set-list (read) ,@vars) - (magic (first (list ,@vars))))) + (magic (first (list ,@vars))) + (first (list ,@vars)))) (defmacro input-string (var) "Read a string input line" `(progn (format t "~&>>> ") (setf ,var (read-line)) - (magic (read-from-string ,var)))) + (magic (read-from-string ,var)) + ,var)) (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -39,28 +58,72 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) -(defmacro magic (var) - "Execute typed-in Lisp code" - (let ((expr (gensym))) - `(when (equalp ,var 'magic) - (progn (simple-input ,expr "[spell]>") - (eval ,expr))))) - (defmacro while (condition &body body) "An implementation of a while loop as found in other languages" `(do () - ((not ,condition)) + ((not ,condition) NIL) ,@body)) +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) -(defun count-instances (search-term search-list) +(defmacro cassoc (entry table &key (test #'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + + +;;; FUNCTIONS + +; Some of these functions are probably quite inefficient (lots of consing) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) + (letter-list-1 NIL) (letter-list-2 NIL)) + ((= c (1- (length s))) + (list (to-string (append letter-list-1)) + (to-string (append letter-list-2 (list letter))))) + (if (< c i) (setf letter-list-1 (append letter-list-1 (list letter))) + (setf letter-list-2 (append letter-list-2 (list letter)))))) + +(defun to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun find-char (c s) + "Find character c in string s and return the index (or NIL if non-existent)" + (dotimes (letter (length s) NIL) + (when (eql (char s letter) c) (return letter)))) + +(defun count-instances (search-term search-list &key (test #'eql)) "Count the number of instances of search-term in search-list" - (do ((lst (cdr (member search-term search-list)) - (cdr (member search-term lst))) - (counter 0 (1+ counter))) - ((null lst) counter))) + (let ((count 0)) + (dolist (item search-list count) + (when (funcall test search-term item) (incf count))))) -; Probably quite inefficient, maybe remove this function later +(defun count-vector-instances (search-term search-vector &key (test #'eql)) + "Count the number of instances of search-term in search-vector" + (let ((count 0)) + (dovector (item search-vector count) + (when (funcall test search-term item) (incf count))))) + (defun to-list (vector) "Turn the vector into a list" (do* ((i 0 (1+ i)) @@ -68,18 +131,12 @@ (lst (list e) (cons e lst))) ((= i (1- (length vector))) (reverse lst)))) -(defun load-file (file-name) - "Load a file into a list of strings (representing the lines)" +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + ;; adds two NIL to the end? (with-open-file (f file-name) (do* ((line (read-line f nil nil) (read-line f nil nil)) (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) - -;; Intended for interactive sessions -;; Load automatically at any clisp start? -(let ((file-name 'util.lisp)) - (defun l (&optional new-file-name) - (when new-file-name (setf file-name new-file-name)) - (load file-name))) diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/lisp/util.lisp b/lisp/util.lisp index 062834a..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -10,6 +10,23 @@ ;;; +;;; MACROS + +(defmacro let-gensyms (syms &body body) + "Gratefully copied from Paul Graham's 'On Lisp'..." + ;; I had to rename it from with-gensyms due to a naming conflict + `(let ,(mapcar #'(lambda (s) + `(,s (gensym))) + syms) + ,@body)) + +(defmacro magic (var) + "Execute typed-in Lisp code" + (let-gensyms (expr) + `(when (equalp ,var 'magic) + (progn (simple-input ,expr "[spell]>") + (eval ,expr))))) + ; potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" @@ -24,14 +41,16 @@ `(progn (format t "~&>>> ") (set-list (read) ,@vars) - (magic (first (list ,@vars))))) + (magic (first (list ,@vars))) + (first (list ,@vars)))) (defmacro input-string (var) "Read a string input line" `(progn (format t "~&>>> ") (setf ,var (read-line)) - (magic (read-from-string ,var)))) + (magic (read-from-string ,var)) + ,var)) (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -39,28 +58,72 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) -(defmacro magic (var) - "Execute typed-in Lisp code" - (let ((expr (gensym))) - `(when (equalp ,var 'magic) - (progn (simple-input ,expr "[spell]>") - (eval ,expr))))) - (defmacro while (condition &body body) "An implementation of a while loop as found in other languages" `(do () - ((not ,condition)) + ((not ,condition) NIL) ,@body)) +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) -(defun count-instances (search-term search-list) +(defmacro cassoc (entry table &key (test #'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + + +;;; FUNCTIONS + +; Some of these functions are probably quite inefficient (lots of consing) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) + (letter-list-1 NIL) (letter-list-2 NIL)) + ((= c (1- (length s))) + (list (to-string (append letter-list-1)) + (to-string (append letter-list-2 (list letter))))) + (if (< c i) (setf letter-list-1 (append letter-list-1 (list letter))) + (setf letter-list-2 (append letter-list-2 (list letter)))))) + +(defun to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun find-char (c s) + "Find character c in string s and return the index (or NIL if non-existent)" + (dotimes (letter (length s) NIL) + (when (eql (char s letter) c) (return letter)))) + +(defun count-instances (search-term search-list &key (test #'eql)) "Count the number of instances of search-term in search-list" - (do ((lst (cdr (member search-term search-list)) - (cdr (member search-term lst))) - (counter 0 (1+ counter))) - ((null lst) counter))) + (let ((count 0)) + (dolist (item search-list count) + (when (funcall test search-term item) (incf count))))) -; Probably quite inefficient, maybe remove this function later +(defun count-vector-instances (search-term search-vector &key (test #'eql)) + "Count the number of instances of search-term in search-vector" + (let ((count 0)) + (dovector (item search-vector count) + (when (funcall test search-term item) (incf count))))) + (defun to-list (vector) "Turn the vector into a list" (do* ((i 0 (1+ i)) @@ -68,18 +131,12 @@ (lst (list e) (cons e lst))) ((= i (1- (length vector))) (reverse lst)))) -(defun load-file (file-name) - "Load a file into a list of strings (representing the lines)" +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + ;; adds two NIL to the end? (with-open-file (f file-name) (do* ((line (read-line f nil nil) (read-line f nil nil)) (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) - -;; Intended for interactive sessions -;; Load automatically at any clisp start? -(let ((file-name 'util.lisp)) - (defun l (&optional new-file-name) - (when new-file-name (setf file-name new-file-name)) - (load file-name))) diff --git a/lisp/world.lisp b/lisp/world.lisp new file mode 100644 index 0000000..6fd4639 --- /dev/null +++ b/lisp/world.lisp @@ -0,0 +1,42 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The world stores the current state of the game. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + + +(defvar *world*) + + +;; The world is basically a list of struct instances representing +;; each object created in this game + +(defstruct world + (players NIL) + (places NIL) + (monsters NIL) + (npcs NIL) + (items NIL)) + +(setf *world* (make-world)) + + +;FIXME Needs work +(defmacro add-game-object (game-object) + "Add a game-object to the *world*" + (let ((attribute-list + (cond ((player-p game-object) '(world-players *world*)) + ((place-p game-object) '(world-places *world*)) + ((monster-p game-object) '(world-monsters *world*)) + ((npc-p game-object) '(world-npcs *world*)) + ((item-p game-object) '(world-items *world*))))) + `(setf ,attribute-list (append ,attribute-list ,game-object)))) + +; TODO +(defmacro get-game-object (object-name)) + diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/lisp/util.lisp b/lisp/util.lisp index 062834a..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -10,6 +10,23 @@ ;;; +;;; MACROS + +(defmacro let-gensyms (syms &body body) + "Gratefully copied from Paul Graham's 'On Lisp'..." + ;; I had to rename it from with-gensyms due to a naming conflict + `(let ,(mapcar #'(lambda (s) + `(,s (gensym))) + syms) + ,@body)) + +(defmacro magic (var) + "Execute typed-in Lisp code" + (let-gensyms (expr) + `(when (equalp ,var 'magic) + (progn (simple-input ,expr "[spell]>") + (eval ,expr))))) + ; potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" @@ -24,14 +41,16 @@ `(progn (format t "~&>>> ") (set-list (read) ,@vars) - (magic (first (list ,@vars))))) + (magic (first (list ,@vars))) + (first (list ,@vars)))) (defmacro input-string (var) "Read a string input line" `(progn (format t "~&>>> ") (setf ,var (read-line)) - (magic (read-from-string ,var)))) + (magic (read-from-string ,var)) + ,var)) (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -39,28 +58,72 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) -(defmacro magic (var) - "Execute typed-in Lisp code" - (let ((expr (gensym))) - `(when (equalp ,var 'magic) - (progn (simple-input ,expr "[spell]>") - (eval ,expr))))) - (defmacro while (condition &body body) "An implementation of a while loop as found in other languages" `(do () - ((not ,condition)) + ((not ,condition) NIL) ,@body)) +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) -(defun count-instances (search-term search-list) +(defmacro cassoc (entry table &key (test #'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + + +;;; FUNCTIONS + +; Some of these functions are probably quite inefficient (lots of consing) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) + (letter-list-1 NIL) (letter-list-2 NIL)) + ((= c (1- (length s))) + (list (to-string (append letter-list-1)) + (to-string (append letter-list-2 (list letter))))) + (if (< c i) (setf letter-list-1 (append letter-list-1 (list letter))) + (setf letter-list-2 (append letter-list-2 (list letter)))))) + +(defun to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun find-char (c s) + "Find character c in string s and return the index (or NIL if non-existent)" + (dotimes (letter (length s) NIL) + (when (eql (char s letter) c) (return letter)))) + +(defun count-instances (search-term search-list &key (test #'eql)) "Count the number of instances of search-term in search-list" - (do ((lst (cdr (member search-term search-list)) - (cdr (member search-term lst))) - (counter 0 (1+ counter))) - ((null lst) counter))) + (let ((count 0)) + (dolist (item search-list count) + (when (funcall test search-term item) (incf count))))) -; Probably quite inefficient, maybe remove this function later +(defun count-vector-instances (search-term search-vector &key (test #'eql)) + "Count the number of instances of search-term in search-vector" + (let ((count 0)) + (dovector (item search-vector count) + (when (funcall test search-term item) (incf count))))) + (defun to-list (vector) "Turn the vector into a list" (do* ((i 0 (1+ i)) @@ -68,18 +131,12 @@ (lst (list e) (cons e lst))) ((= i (1- (length vector))) (reverse lst)))) -(defun load-file (file-name) - "Load a file into a list of strings (representing the lines)" +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + ;; adds two NIL to the end? (with-open-file (f file-name) (do* ((line (read-line f nil nil) (read-line f nil nil)) (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) - -;; Intended for interactive sessions -;; Load automatically at any clisp start? -(let ((file-name 'util.lisp)) - (defun l (&optional new-file-name) - (when new-file-name (setf file-name new-file-name)) - (load file-name))) diff --git a/lisp/world.lisp b/lisp/world.lisp new file mode 100644 index 0000000..6fd4639 --- /dev/null +++ b/lisp/world.lisp @@ -0,0 +1,42 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The world stores the current state of the game. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + + +(defvar *world*) + + +;; The world is basically a list of struct instances representing +;; each object created in this game + +(defstruct world + (players NIL) + (places NIL) + (monsters NIL) + (npcs NIL) + (items NIL)) + +(setf *world* (make-world)) + + +;FIXME Needs work +(defmacro add-game-object (game-object) + "Add a game-object to the *world*" + (let ((attribute-list + (cond ((player-p game-object) '(world-players *world*)) + ((place-p game-object) '(world-places *world*)) + ((monster-p game-object) '(world-monsters *world*)) + ((npc-p game-object) '(world-npcs *world*)) + ((item-p game-object) '(world-items *world*))))) + `(setf ,attribute-list (append ,attribute-list ,game-object)))) + +; TODO +(defmacro get-game-object (object-name)) + diff --git a/worlds/Example/example.atl b/worlds/Example/example.atl deleted file mode 100644 index 3101edd..0000000 --- a/worlds/Example/example.atl +++ /dev/null @@ -1,72 +0,0 @@ -# This is an example Atlantis file - I use it to explore what the language -# should end up looking like. - -load races.atl -load classes.atl - - -define-quest Kill hellhound - objective kill hellhound - reward gold 300 - - -define-spell Ray of death - type kill - min-intelligence 12 - success-rate 35 - - -define-npc Hades - say Oh, amazing, you actually got here! Who did you bribe? - sell 30 Ambrosia - quest Kill hellhound - - -define-item Scroll of light - value 80 - add-experience 20 - add-spell Ray of death - -define-item Ambrosia - category Food - add-health 5 - - -define-monster Hellhound - armor-class 8 - strength 10 - melee-weapon 2 claws - experience 50 - spawn 2 - aggression 60 - -define-monster Fury - armor-class 5 - strength 8 - melee-weapon 10 fire whip - experience 74 - spawn 0.8 - aggression 30 - - -define-place Nowhere - description Welcome to Nowhere! You are in the void, the space between \ -the worlds. Around you is black. Black, except for one tiny pin-prick of light \ -to the north. - neighbour Elysium - -define-place Elysium - description Congratulations! You have achieved Elysium! - neighbour Nowhere - neighbour Fields of punishment - npc Hades - item Scroll of light - -define-place Fields of punishment - description You really, really don't want to end up here... - neighbour Elysium - monster Hellhound - monster Fury - - -start-place Elysium \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/lisp/util.lisp b/lisp/util.lisp index 062834a..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -10,6 +10,23 @@ ;;; +;;; MACROS + +(defmacro let-gensyms (syms &body body) + "Gratefully copied from Paul Graham's 'On Lisp'..." + ;; I had to rename it from with-gensyms due to a naming conflict + `(let ,(mapcar #'(lambda (s) + `(,s (gensym))) + syms) + ,@body)) + +(defmacro magic (var) + "Execute typed-in Lisp code" + (let-gensyms (expr) + `(when (equalp ,var 'magic) + (progn (simple-input ,expr "[spell]>") + (eval ,expr))))) + ; potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" @@ -24,14 +41,16 @@ `(progn (format t "~&>>> ") (set-list (read) ,@vars) - (magic (first (list ,@vars))))) + (magic (first (list ,@vars))) + (first (list ,@vars)))) (defmacro input-string (var) "Read a string input line" `(progn (format t "~&>>> ") (setf ,var (read-line)) - (magic (read-from-string ,var)))) + (magic (read-from-string ,var)) + ,var)) (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -39,28 +58,72 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) -(defmacro magic (var) - "Execute typed-in Lisp code" - (let ((expr (gensym))) - `(when (equalp ,var 'magic) - (progn (simple-input ,expr "[spell]>") - (eval ,expr))))) - (defmacro while (condition &body body) "An implementation of a while loop as found in other languages" `(do () - ((not ,condition)) + ((not ,condition) NIL) ,@body)) +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) -(defun count-instances (search-term search-list) +(defmacro cassoc (entry table &key (test #'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + + +;;; FUNCTIONS + +; Some of these functions are probably quite inefficient (lots of consing) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) + (letter-list-1 NIL) (letter-list-2 NIL)) + ((= c (1- (length s))) + (list (to-string (append letter-list-1)) + (to-string (append letter-list-2 (list letter))))) + (if (< c i) (setf letter-list-1 (append letter-list-1 (list letter))) + (setf letter-list-2 (append letter-list-2 (list letter)))))) + +(defun to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun find-char (c s) + "Find character c in string s and return the index (or NIL if non-existent)" + (dotimes (letter (length s) NIL) + (when (eql (char s letter) c) (return letter)))) + +(defun count-instances (search-term search-list &key (test #'eql)) "Count the number of instances of search-term in search-list" - (do ((lst (cdr (member search-term search-list)) - (cdr (member search-term lst))) - (counter 0 (1+ counter))) - ((null lst) counter))) + (let ((count 0)) + (dolist (item search-list count) + (when (funcall test search-term item) (incf count))))) -; Probably quite inefficient, maybe remove this function later +(defun count-vector-instances (search-term search-vector &key (test #'eql)) + "Count the number of instances of search-term in search-vector" + (let ((count 0)) + (dovector (item search-vector count) + (when (funcall test search-term item) (incf count))))) + (defun to-list (vector) "Turn the vector into a list" (do* ((i 0 (1+ i)) @@ -68,18 +131,12 @@ (lst (list e) (cons e lst))) ((= i (1- (length vector))) (reverse lst)))) -(defun load-file (file-name) - "Load a file into a list of strings (representing the lines)" +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + ;; adds two NIL to the end? (with-open-file (f file-name) (do* ((line (read-line f nil nil) (read-line f nil nil)) (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) - -;; Intended for interactive sessions -;; Load automatically at any clisp start? -(let ((file-name 'util.lisp)) - (defun l (&optional new-file-name) - (when new-file-name (setf file-name new-file-name)) - (load file-name))) diff --git a/lisp/world.lisp b/lisp/world.lisp new file mode 100644 index 0000000..6fd4639 --- /dev/null +++ b/lisp/world.lisp @@ -0,0 +1,42 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The world stores the current state of the game. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + + +(defvar *world*) + + +;; The world is basically a list of struct instances representing +;; each object created in this game + +(defstruct world + (players NIL) + (places NIL) + (monsters NIL) + (npcs NIL) + (items NIL)) + +(setf *world* (make-world)) + + +;FIXME Needs work +(defmacro add-game-object (game-object) + "Add a game-object to the *world*" + (let ((attribute-list + (cond ((player-p game-object) '(world-players *world*)) + ((place-p game-object) '(world-places *world*)) + ((monster-p game-object) '(world-monsters *world*)) + ((npc-p game-object) '(world-npcs *world*)) + ((item-p game-object) '(world-items *world*))))) + `(setf ,attribute-list (append ,attribute-list ,game-object)))) + +; TODO +(defmacro get-game-object (object-name)) + diff --git a/worlds/Example/example.atl b/worlds/Example/example.atl deleted file mode 100644 index 3101edd..0000000 --- a/worlds/Example/example.atl +++ /dev/null @@ -1,72 +0,0 @@ -# This is an example Atlantis file - I use it to explore what the language -# should end up looking like. - -load races.atl -load classes.atl - - -define-quest Kill hellhound - objective kill hellhound - reward gold 300 - - -define-spell Ray of death - type kill - min-intelligence 12 - success-rate 35 - - -define-npc Hades - say Oh, amazing, you actually got here! Who did you bribe? - sell 30 Ambrosia - quest Kill hellhound - - -define-item Scroll of light - value 80 - add-experience 20 - add-spell Ray of death - -define-item Ambrosia - category Food - add-health 5 - - -define-monster Hellhound - armor-class 8 - strength 10 - melee-weapon 2 claws - experience 50 - spawn 2 - aggression 60 - -define-monster Fury - armor-class 5 - strength 8 - melee-weapon 10 fire whip - experience 74 - spawn 0.8 - aggression 30 - - -define-place Nowhere - description Welcome to Nowhere! You are in the void, the space between \ -the worlds. Around you is black. Black, except for one tiny pin-prick of light \ -to the north. - neighbour Elysium - -define-place Elysium - description Congratulations! You have achieved Elysium! - neighbour Nowhere - neighbour Fields of punishment - npc Hades - item Scroll of light - -define-place Fields of punishment - description You really, really don't want to end up here... - neighbour Elysium - monster Hellhound - monster Fury - - -start-place Elysium \ No newline at end of file diff --git a/worlds/Example/test.atl b/worlds/Example/test.atl deleted file mode 100644 index 7c112b8..0000000 --- a/worlds/Example/test.atl +++ /dev/null @@ -1,18 +0,0 @@ -# This is a simple test ATL file to test whatever I have implemented so far. -# @author Daniel Vedder -# @date 04/05/2015 - -define-place Nowhere - description Welcome to Nowhere! -#You are in the void, the space between \ -#the worlds. Around you is black. Black, except for one tiny pin-prick of -#light \to the north. - neighbour Elysium - -define-place Elysium - description This is where you want to be when you are six feet under... - neighbour Nowhere - -load test2.atl - -start-place Nowhere \ No newline at end of file diff --git a/ATL/example.atl b/ATL/example.atl new file mode 100644 index 0000000..3101edd --- /dev/null +++ b/ATL/example.atl @@ -0,0 +1,72 @@ +# This is an example Atlantis file - I use it to explore what the language +# should end up looking like. + +load races.atl +load classes.atl + + +define-quest Kill hellhound + objective kill hellhound + reward gold 300 + + +define-spell Ray of death + type kill + min-intelligence 12 + success-rate 35 + + +define-npc Hades + say Oh, amazing, you actually got here! Who did you bribe? + sell 30 Ambrosia + quest Kill hellhound + + +define-item Scroll of light + value 80 + add-experience 20 + add-spell Ray of death + +define-item Ambrosia + category Food + add-health 5 + + +define-monster Hellhound + armor-class 8 + strength 10 + melee-weapon 2 claws + experience 50 + spawn 2 + aggression 60 + +define-monster Fury + armor-class 5 + strength 8 + melee-weapon 10 fire whip + experience 74 + spawn 0.8 + aggression 30 + + +define-place Nowhere + description Welcome to Nowhere! You are in the void, the space between \ +the worlds. Around you is black. Black, except for one tiny pin-prick of light \ +to the north. + neighbour Elysium + +define-place Elysium + description Congratulations! You have achieved Elysium! + neighbour Nowhere + neighbour Fields of punishment + npc Hades + item Scroll of light + +define-place Fields of punishment + description You really, really don't want to end up here... + neighbour Elysium + monster Hellhound + monster Fury + + +start-place Elysium \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl new file mode 100644 index 0000000..7fc97d1 --- /dev/null +++ b/ATL/lisp-test.atl @@ -0,0 +1,18 @@ +; This is a simple test ATL file to test whatever I have implemented so far. +; @author Daniel Vedder +; @date 04/05/2015 + +define-place "Nowhere" + description "Welcome to Nowhere! +You are in the void, the space between +the worlds. Around you is black. Black, except for one tiny pin-prick of +light to the north." + neighbour "Elysium" + +define-place "Elysium" + description "This is where you want to be when you are six feet under..." + neighbour "Nowhere" + +load-file lisp-test2.atl + +start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test2.atl b/ATL/lisp-test2.atl new file mode 100644 index 0000000..bf51367 --- /dev/null +++ b/ATL/lisp-test2.atl @@ -0,0 +1,8 @@ +; Just a very small file to test whether the load function works properly +; @author Daniel Vedder +; @date 18/05/2015 + +define-place "Fields of Punishment" + description "Precisely where you do NOT want to end up..." + neighbour "Nowhere" + neighbour "Elysium" diff --git a/ATL/test.atl b/ATL/test.atl new file mode 100644 index 0000000..7c112b8 --- /dev/null +++ b/ATL/test.atl @@ -0,0 +1,18 @@ +# This is a simple test ATL file to test whatever I have implemented so far. +# @author Daniel Vedder +# @date 04/05/2015 + +define-place Nowhere + description Welcome to Nowhere! +#You are in the void, the space between \ +#the worlds. Around you is black. Black, except for one tiny pin-prick of +#light \to the north. + neighbour Elysium + +define-place Elysium + description This is where you want to be when you are six feet under... + neighbour Nowhere + +load test2.atl + +start-place Nowhere \ No newline at end of file diff --git a/ATL/test2.atl b/ATL/test2.atl new file mode 100644 index 0000000..41dda7a --- /dev/null +++ b/ATL/test2.atl @@ -0,0 +1,6 @@ +# This file is used to test the load command + +define-place Fields of Punishment + description You really, really don't want to be here! + neighbour Nowhere + neighbour Elysium \ No newline at end of file diff --git a/atlantis.txt b/atlantis.txt new file mode 100644 index 0000000..bd3c80a --- /dev/null +++ b/atlantis.txt @@ -0,0 +1,33 @@ +====================== +|| Project ATLANTIS || +====================== + + +SUMMARY + +Atlantis is a framework for creating an MUD world. It includes an interpreted +language for describing places, items, monsters and NPCs; provides networking +capabilities for multiplayer games and a text-based user interface. Inspired +and influenced by OpenWorld, it is written in Common Lisp. + + +GAME PLAY + +The player navigates around the world using shell-like commands. He is immersed +into the game by DnD-like descriptive texts; new areas of the world can be +generated on the fly by the players themselves. It is a RPG, players aim to +develop their characters and can interact with other players. + + +TECHNICAL CHALLENGES + +Networking: does Common Lisp support networking? threading? If not the latter, + how can a real-time networked multiplayer game be implemented? + +Implementation: presumably Atlantis will have to have a server-client model. + How do the two communicate? Are there alternative implementation + models? + +Language: how complex should the inbuilt language be? Is it a purely descriptive + language similar to XML/INI files? Should it be possible to script + actions with it? Must it be Turing complete? diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index acfa19c..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,9 +7,10 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 0 1)) +(defconstant ATLANTIS-VERSION '(0 1 0)) (load 'util.lisp) +(load 'interpreter.lisp) (defun start-server () @@ -19,7 +20,9 @@ (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~A on port ~A" (string world-file) port)) + (format t "~&Loading file ~S on port ~A" world-file port) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -39,16 +42,18 @@ (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) - (format t "~&Copyright (c)2015 Daniel Vedder") - (format t "~&Licensed under the terms of the MIT license.")) + (format t "~&Copyright (c) 2015 Daniel Vedder") + (format t "~&Licensed under the terms of the MIT license.~%")) + +(defun print-help () + (print-version) + (format t "~%~%Sorry, the help is not yet available!")) (defun start-menu () "Show the start menu and take a choice from the user" - (with-open-file (logo "banner.txt") - (do ((line (read-line logo nil nil) - (read-line logo nil nil))) - ((null line)) - (format t "~&~A" line))) + (let ((logo (load-text-file "banner.txt"))) + (dolist (line logo) + (unless (null line) (format t "~%~A" line)))) (format t "~&~%Welcome! What do you want to do?") (format t "~&-> (S)tart a server") (format t "~&-> (J)oin a game") @@ -62,4 +67,14 @@ ((equalp choice 'e) (format t "~&Goodbye!") (quit)))) -(start-menu) +;; TODO +;; (defun parse-commandline-args () +;; (do* ((i 0 (1+ i)) (a (nth i *args*) (nth i *args)) +;; (param-functions '(("--version" (#'print-version 0)) +;; ("--help" (#'print-help 0)) +;; ("--server" + + +(if *args* + (parse-commandline-args) + (start-menu)) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp new file mode 100644 index 0000000..9377894 --- /dev/null +++ b/lisp/game-objects.lisp @@ -0,0 +1,49 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file contains all the various kinds of in-game objects like +;;; places, monsters, NPCs, items... +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct place + (name "") + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) + +;;; INCOMPLETE STRUCTS >>> + +(defstruct npc + (name "") + (says "")) + +(defstruct monster + (name "") + (description "") + (strength 0) + (armour-class 0)) + +(defstruct item + (name "") + (description "") + (function NIL)) + +(defun set-object-attribute (game-object property value) + "Set the attribute 'property' of 'game-object' to 'value'" + ;; Here follows Lisp magic :D (that took ages to get right...) + ;; I'm not sure how elegant it is to call (eval) explicitly, but in this + ;; case I couldn't avoid it - I needed a mix between a macro and a function + (let ((command (read-from-string + (concatenate 'string + (symbol-name (type-of game-object)) + "-" (if (stringp property) property + (symbol-name property)))))) + (eval `(setf (,command ,game-object) ,value)))) + diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index 8265344..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -8,3 +8,67 @@ ;;; author: Daniel Vedder ;;; date: 09/05/2015 ;;; + +(load 'util.lisp) +(load 'game-objects.lisp) +(load 'player.lisp) +(load 'world.lisp) + + +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) + +(defun define-place (name) + (format t "~&Making place ~A" name) + (make-place :name name)) + +(defun start-place (place) + ;not yet defined + NIL + ) + +(let ((world-directory NIL)) + (defun load-file (file-name) + "Load and interpret an ATL source file" + ; save/load the current working directory + (if (null (pathname-directory file-name)) + (setf file-name (make-pathname + :directory world-directory + :name (pathname-name file-name) + :type (pathname-type file-name))) + (setf world-directory (pathname-directory file-name))) + ; parse the ATL file + (do* ((line-nr 0 (1+ line-nr)) (source (load-text-file file-name)) + (line (nth line-nr source) (nth line-nr source)) + (current-object NIL)) + ((= line-nr (length source)) NIL) + ;concatenate string arguments spanning several lines + (while (= (count-vector-instances #\" line) 1) + (incf line-nr) + (setf line (concatenate 'string line (nth line-nr source)))) + (cond ((zerop (length line)) + (when current-object (add-game-object current-object)) + (setf current-object NIL)) + ; interpret a define command + ((not (or (eql (aref line 0) #\;) + (eql (aref line 0) #\SPACE) + (eql (aref line 0) #\TAB))) + (setf current-object (funcall (symbol-function + (read-from-string line)) + ; here follows a kludge to work around a clisp bug (the + ; :start keyword in read-from-string is not recognized) + (read-from-string (second + (cut-string line (find-char #\space line))))))) + ; interpret an option command + ((or (eql (aref line 0) #\Space) + (eql (aref line 0) #\Tab)) + (setf line (string-left-trim '(#\Space #\Tab) line)) + (set-object-attribute current-object (read-from-string line) + (read-from-string + (second (cut-string line (find-char #\space line)))))) + ((eql (aref line 0) #\;)) ;Comments are ignored + (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" + ;can't happen + (1+ line-nr) line)))))) diff --git a/lisp/player.lisp b/lisp/player.lisp new file mode 100644 index 0000000..220e23b --- /dev/null +++ b/lisp/player.lisp @@ -0,0 +1,21 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This file represents a single player. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + +(defstruct player + (name "") + (race NIL) + (class NIL) + (strength 0) + (dexterity 0) + (constitution 0) + (intelligence 0) + (items NIL) + (weapons NIL)) diff --git a/lisp/util.lisp b/lisp/util.lisp index 062834a..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -10,6 +10,23 @@ ;;; +;;; MACROS + +(defmacro let-gensyms (syms &body body) + "Gratefully copied from Paul Graham's 'On Lisp'..." + ;; I had to rename it from with-gensyms due to a naming conflict + `(let ,(mapcar #'(lambda (s) + `(,s (gensym))) + syms) + ,@body)) + +(defmacro magic (var) + "Execute typed-in Lisp code" + (let-gensyms (expr) + `(when (equalp ,var 'magic) + (progn (simple-input ,expr "[spell]>") + (eval ,expr))))) + ; potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" @@ -24,14 +41,16 @@ `(progn (format t "~&>>> ") (set-list (read) ,@vars) - (magic (first (list ,@vars))))) + (magic (first (list ,@vars))) + (first (list ,@vars)))) (defmacro input-string (var) "Read a string input line" `(progn (format t "~&>>> ") (setf ,var (read-line)) - (magic (read-from-string ,var)))) + (magic (read-from-string ,var)) + ,var)) (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -39,28 +58,72 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) -(defmacro magic (var) - "Execute typed-in Lisp code" - (let ((expr (gensym))) - `(when (equalp ,var 'magic) - (progn (simple-input ,expr "[spell]>") - (eval ,expr))))) - (defmacro while (condition &body body) "An implementation of a while loop as found in other languages" `(do () - ((not ,condition)) + ((not ,condition) NIL) ,@body)) +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) -(defun count-instances (search-term search-list) +(defmacro cassoc (entry table &key (test #'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + + +;;; FUNCTIONS + +; Some of these functions are probably quite inefficient (lots of consing) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) + (letter-list-1 NIL) (letter-list-2 NIL)) + ((= c (1- (length s))) + (list (to-string (append letter-list-1)) + (to-string (append letter-list-2 (list letter))))) + (if (< c i) (setf letter-list-1 (append letter-list-1 (list letter))) + (setf letter-list-2 (append letter-list-2 (list letter)))))) + +(defun to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun find-char (c s) + "Find character c in string s and return the index (or NIL if non-existent)" + (dotimes (letter (length s) NIL) + (when (eql (char s letter) c) (return letter)))) + +(defun count-instances (search-term search-list &key (test #'eql)) "Count the number of instances of search-term in search-list" - (do ((lst (cdr (member search-term search-list)) - (cdr (member search-term lst))) - (counter 0 (1+ counter))) - ((null lst) counter))) + (let ((count 0)) + (dolist (item search-list count) + (when (funcall test search-term item) (incf count))))) -; Probably quite inefficient, maybe remove this function later +(defun count-vector-instances (search-term search-vector &key (test #'eql)) + "Count the number of instances of search-term in search-vector" + (let ((count 0)) + (dovector (item search-vector count) + (when (funcall test search-term item) (incf count))))) + (defun to-list (vector) "Turn the vector into a list" (do* ((i 0 (1+ i)) @@ -68,18 +131,12 @@ (lst (list e) (cons e lst))) ((= i (1- (length vector))) (reverse lst)))) -(defun load-file (file-name) - "Load a file into a list of strings (representing the lines)" +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + ;; adds two NIL to the end? (with-open-file (f file-name) (do* ((line (read-line f nil nil) (read-line f nil nil)) (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) - -;; Intended for interactive sessions -;; Load automatically at any clisp start? -(let ((file-name 'util.lisp)) - (defun l (&optional new-file-name) - (when new-file-name (setf file-name new-file-name)) - (load file-name))) diff --git a/lisp/world.lisp b/lisp/world.lisp new file mode 100644 index 0000000..6fd4639 --- /dev/null +++ b/lisp/world.lisp @@ -0,0 +1,42 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The world stores the current state of the game. +;;; +;;; Licensed under the terms of the MIT license +;;; author: Daniel Vedder +;;; date: 15/05/2015 +;;; + + +(defvar *world*) + + +;; The world is basically a list of struct instances representing +;; each object created in this game + +(defstruct world + (players NIL) + (places NIL) + (monsters NIL) + (npcs NIL) + (items NIL)) + +(setf *world* (make-world)) + + +;FIXME Needs work +(defmacro add-game-object (game-object) + "Add a game-object to the *world*" + (let ((attribute-list + (cond ((player-p game-object) '(world-players *world*)) + ((place-p game-object) '(world-places *world*)) + ((monster-p game-object) '(world-monsters *world*)) + ((npc-p game-object) '(world-npcs *world*)) + ((item-p game-object) '(world-items *world*))))) + `(setf ,attribute-list (append ,attribute-list ,game-object)))) + +; TODO +(defmacro get-game-object (object-name)) + diff --git a/worlds/Example/example.atl b/worlds/Example/example.atl deleted file mode 100644 index 3101edd..0000000 --- a/worlds/Example/example.atl +++ /dev/null @@ -1,72 +0,0 @@ -# This is an example Atlantis file - I use it to explore what the language -# should end up looking like. - -load races.atl -load classes.atl - - -define-quest Kill hellhound - objective kill hellhound - reward gold 300 - - -define-spell Ray of death - type kill - min-intelligence 12 - success-rate 35 - - -define-npc Hades - say Oh, amazing, you actually got here! Who did you bribe? - sell 30 Ambrosia - quest Kill hellhound - - -define-item Scroll of light - value 80 - add-experience 20 - add-spell Ray of death - -define-item Ambrosia - category Food - add-health 5 - - -define-monster Hellhound - armor-class 8 - strength 10 - melee-weapon 2 claws - experience 50 - spawn 2 - aggression 60 - -define-monster Fury - armor-class 5 - strength 8 - melee-weapon 10 fire whip - experience 74 - spawn 0.8 - aggression 30 - - -define-place Nowhere - description Welcome to Nowhere! You are in the void, the space between \ -the worlds. Around you is black. Black, except for one tiny pin-prick of light \ -to the north. - neighbour Elysium - -define-place Elysium - description Congratulations! You have achieved Elysium! - neighbour Nowhere - neighbour Fields of punishment - npc Hades - item Scroll of light - -define-place Fields of punishment - description You really, really don't want to end up here... - neighbour Elysium - monster Hellhound - monster Fury - - -start-place Elysium \ No newline at end of file diff --git a/worlds/Example/test.atl b/worlds/Example/test.atl deleted file mode 100644 index 7c112b8..0000000 --- a/worlds/Example/test.atl +++ /dev/null @@ -1,18 +0,0 @@ -# This is a simple test ATL file to test whatever I have implemented so far. -# @author Daniel Vedder -# @date 04/05/2015 - -define-place Nowhere - description Welcome to Nowhere! -#You are in the void, the space between \ -#the worlds. Around you is black. Black, except for one tiny pin-prick of -#light \to the north. - neighbour Elysium - -define-place Elysium - description This is where you want to be when you are six feet under... - neighbour Nowhere - -load test2.atl - -start-place Nowhere \ No newline at end of file diff --git a/worlds/Example/test2.atl b/worlds/Example/test2.atl deleted file mode 100644 index 41dda7a..0000000 --- a/worlds/Example/test2.atl +++ /dev/null @@ -1,6 +0,0 @@ -# This file is used to test the load command - -define-place Fields of Punishment - description You really, really don't want to be here! - neighbour Nowhere - neighbour Elysium \ No newline at end of file