diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +load-file lisp-test2.atl start-place "Nowhere" \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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 e97a035..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -21,7 +21,8 @@ (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) (format t "~&Loading file ~S on port ~A" world-file port) - (load-atl-file world-file)) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -41,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") @@ -64,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/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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 e97a035..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -21,7 +21,8 @@ (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) (format t "~&Loading file ~S on port ~A" world-file port) - (load-atl-file world-file)) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -41,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") @@ -64,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 index ebdb743..9377894 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -12,12 +12,13 @@ (defstruct place (name "") - (neighbours NIL) - (items NIL) - (monsters NIL) - (npcs NIL)) + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) -;;; Temporary, just so I have the structs >>> +;;; INCOMPLETE STRUCTS >>> (defstruct npc (name "") @@ -26,9 +27,23 @@ (defstruct monster (name "") (description "") - (strength 0)) + (strength 0) + (armour-class 0)) (defstruct item (name "") (description "") - (functions NIL)) + (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/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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 e97a035..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -21,7 +21,8 @@ (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) (format t "~&Loading file ~S on port ~A" world-file port) - (load-atl-file world-file)) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -41,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") @@ -64,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 index ebdb743..9377894 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -12,12 +12,13 @@ (defstruct place (name "") - (neighbours NIL) - (items NIL) - (monsters NIL) - (npcs NIL)) + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) -;;; Temporary, just so I have the structs >>> +;;; INCOMPLETE STRUCTS >>> (defstruct npc (name "") @@ -26,9 +27,23 @@ (defstruct monster (name "") (description "") - (strength 0)) + (strength 0) + (armour-class 0)) (defstruct item (name "") (description "") - (functions NIL)) + (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 790105e..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -15,10 +15,10 @@ (load 'world.lisp) -(defun load-atl (atl-file) - ;not yet defined - NIL - ) +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) (defun define-place (name) (format t "~&Making place ~A" name) @@ -29,31 +29,46 @@ NIL ) -(defun load-atl-file (file-name) - "Load an ATL source file" - (do* ((line-nr 0 (1+ line-nr)) (source (load-file file-name)) - (line (nth line-nr source) (nth line-nr source)) - (current-object NIL)) - ((= line-nr (length source)) NIL) - (cond ((zerop (length line)) (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 is 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))))) +(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/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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 e97a035..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -21,7 +21,8 @@ (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) (format t "~&Loading file ~S on port ~A" world-file port) - (load-atl-file world-file)) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -41,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") @@ -64,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 index ebdb743..9377894 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -12,12 +12,13 @@ (defstruct place (name "") - (neighbours NIL) - (items NIL) - (monsters NIL) - (npcs NIL)) + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) -;;; Temporary, just so I have the structs >>> +;;; INCOMPLETE STRUCTS >>> (defstruct npc (name "") @@ -26,9 +27,23 @@ (defstruct monster (name "") (description "") - (strength 0)) + (strength 0) + (armour-class 0)) (defstruct item (name "") (description "") - (functions NIL)) + (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 790105e..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -15,10 +15,10 @@ (load 'world.lisp) -(defun load-atl (atl-file) - ;not yet defined - NIL - ) +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) (defun define-place (name) (format t "~&Making place ~A" name) @@ -29,31 +29,46 @@ NIL ) -(defun load-atl-file (file-name) - "Load an ATL source file" - (do* ((line-nr 0 (1+ line-nr)) (source (load-file file-name)) - (line (nth line-nr source) (nth line-nr source)) - (current-object NIL)) - ((= line-nr (length source)) NIL) - (cond ((zerop (length line)) (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 is 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))))) +(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/util.lisp b/lisp/util.lisp index e09ca2f..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -12,9 +12,17 @@ ;;; 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 ((expr (gensym))) + (let-gensyms (expr) `(when (equalp ,var 'magic) (progn (simple-input ,expr "[spell]>") (eval ,expr))))) @@ -71,7 +79,7 @@ (defmacro dovector ((element vector &optional (return-variable NIL)) &body body) "A macro analogous to dolist" - (let ((index (gensym))) + (let-gensyms (index) `(do* ((,index 0 (1+ ,index)) (,element (safe-aref ,vector ,index) (safe-aref ,vector ,index))) @@ -123,8 +131,8 @@ (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) diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index fdb5416..7fc97d1 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -3,16 +3,16 @@ ; @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. + 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 +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/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 e97a035..0bbe63b 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -21,7 +21,8 @@ (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) (format t "~&Loading file ~S on port ~A" world-file port) - (load-atl-file world-file)) + (load-file world-file) + (break)) (defun join-game () "Join a running game on the server" @@ -41,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") @@ -64,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 index ebdb743..9377894 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -12,12 +12,13 @@ (defstruct place (name "") - (neighbours NIL) - (items NIL) - (monsters NIL) - (npcs NIL)) + (description "") + (neighbour NIL) + (item NIL) + (monster NIL) + (npc NIL)) -;;; Temporary, just so I have the structs >>> +;;; INCOMPLETE STRUCTS >>> (defstruct npc (name "") @@ -26,9 +27,23 @@ (defstruct monster (name "") (description "") - (strength 0)) + (strength 0) + (armour-class 0)) (defstruct item (name "") (description "") - (functions NIL)) + (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 790105e..d4b1721 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -15,10 +15,10 @@ (load 'world.lisp) -(defun load-atl (atl-file) - ;not yet defined - NIL - ) +;; (defun load-atl (atl-file) +;; ;not yet defined +;; NIL +;; ) (defun define-place (name) (format t "~&Making place ~A" name) @@ -29,31 +29,46 @@ NIL ) -(defun load-atl-file (file-name) - "Load an ATL source file" - (do* ((line-nr 0 (1+ line-nr)) (source (load-file file-name)) - (line (nth line-nr source) (nth line-nr source)) - (current-object NIL)) - ((= line-nr (length source)) NIL) - (cond ((zerop (length line)) (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 is 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))))) +(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/util.lisp b/lisp/util.lisp index e09ca2f..e5434a8 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -12,9 +12,17 @@ ;;; 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 ((expr (gensym))) + (let-gensyms (expr) `(when (equalp ,var 'magic) (progn (simple-input ,expr "[spell]>") (eval ,expr))))) @@ -71,7 +79,7 @@ (defmacro dovector ((element vector &optional (return-variable NIL)) &body body) "A macro analogous to dolist" - (let ((index (gensym))) + (let-gensyms (index) `(do* ((,index 0 (1+ ,index)) (,element (safe-aref ,vector ,index) (safe-aref ,vector ,index))) @@ -123,8 +131,8 @@ (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) diff --git a/lisp/world.lisp b/lisp/world.lisp index 48a2dac..6fd4639 100644 --- a/lisp/world.lisp +++ b/lisp/world.lisp @@ -25,17 +25,6 @@ (setf *world* (make-world)) -;; (defmacro add-game-object (game-object) -;; "Add a game-object to the *world*" -;; (let ((object-function -;; (cond ((playerp game-object) 'world-players) -;; ((place-p game-object) 'world-places) -;; ((monster-p game-object) 'world-monsters) -;; ((npc-p game-object) 'world-npcs) -;; ((itemp-p game-object) 'world-items)))) -;; `(setf (,object-function *world*) -;; (append (,object-function *world*) ,game-object)))) - ;FIXME Needs work (defmacro add-game-object (game-object) @@ -51,5 +40,3 @@ ; TODO (defmacro get-game-object (object-name)) -;TODO -(defmacro set-object-attribute (game-object property value))