diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..622f9a8 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,15 +7,14 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 2)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") (defun development () @@ -37,7 +36,7 @@ "Start a new game on a server" (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (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 ~S on port ~A" world-file port) @@ -61,6 +60,11 @@ (play-game name)) +(defun single-player () + "Start a single-player game" + ;; TODO + ) + (defun print-version () (format t "~&Lisp Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) @@ -74,23 +78,18 @@ (dolist (line (load-text-file "banner.txt")) (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") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) + (setf options '("Start a server" "Join a game" "Play single-player" + "Develop" "About" "Exit")) + (setf choice (choose-option options)) (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (0 (start-server)) + (1 (join-game)) + (2 (single-player)) + (3 (development)) + (4 (print-version) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) + (5 (format t "~&Goodbye!") + (quit)))) (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..622f9a8 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,15 +7,14 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 2)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") (defun development () @@ -37,7 +36,7 @@ "Start a new game on a server" (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (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 ~S on port ~A" world-file port) @@ -61,6 +60,11 @@ (play-game name)) +(defun single-player () + "Start a single-player game" + ;; TODO + ) + (defun print-version () (format t "~&Lisp Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) @@ -74,23 +78,18 @@ (dolist (line (load-text-file "banner.txt")) (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") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) + (setf options '("Start a server" "Join a game" "Play single-player" + "Develop" "About" "Exit")) + (setf choice (choose-option options)) (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (0 (start-server)) + (1 (join-game)) + (2 (single-player)) + (3 (development)) + (4 (print-version) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) + (5 (format t "~&Goodbye!") + (quit)))) (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..622f9a8 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,15 +7,14 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 2)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") (defun development () @@ -37,7 +36,7 @@ "Start a new game on a server" (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (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 ~S on port ~A" world-file port) @@ -61,6 +60,11 @@ (play-game name)) +(defun single-player () + "Start a single-player game" + ;; TODO + ) + (defun print-version () (format t "~&Lisp Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) @@ -74,23 +78,18 @@ (dolist (line (load-text-file "banner.txt")) (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") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) + (setf options '("Start a server" "Join a game" "Play single-player" + "Develop" "About" "Exit")) + (setf choice (choose-option options)) (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (0 (start-server)) + (1 (join-game)) + (2 (single-player)) + (3 (development)) + (4 (print-version) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) + (5 (format t "~&Goodbye!") + (quit)))) (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..622f9a8 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,15 +7,14 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 2)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") (defun development () @@ -37,7 +36,7 @@ "Start a new game on a server" (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (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 ~S on port ~A" world-file port) @@ -61,6 +60,11 @@ (play-game name)) +(defun single-player () + "Start a single-player game" + ;; TODO + ) + (defun print-version () (format t "~&Lisp Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) @@ -74,23 +78,18 @@ (dolist (line (load-text-file "banner.txt")) (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") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) + (setf options '("Start a server" "Join a game" "Play single-player" + "Develop" "About" "Exit")) + (setf choice (choose-option options)) (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (0 (start-server)) + (1 (join-game)) + (2 (single-player)) + (3 (development)) + (4 (print-version) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) + (5 (format t "~&Goodbye!") + (quit)))) (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/ui.lisp b/lisp/ui.lisp new file mode 100644 index 0000000..e51c597 --- /dev/null +++ b/lisp/ui.lisp @@ -0,0 +1,188 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The client module is responsible for the actual user interface presented +;;; to a player. (Warning: this will likely change significantly, currently +;;; I am only implementing a mock-up before I get the networking part working.) +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 21/05/2015 +;;; + +(let ((player NIL)) + (defun play-game (player-name) + "The main game loop" + ;(update-world) + ;; Initialize the player if necessary + (when (null player) + (setf player (get-game-object 'player player-name))) + (when (null player) + (setf player (create-player player-name)) + (when (null (list-world-objects 'player)) + (setf (world-game-manager *world*) (player-name player))) + (add-game-object player) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player))) + ;; The actual game loop + (let ((place (get-game-object 'place (player-place player)))) + (describe-place place) + (input-string command) + (while (not (or (equalp command "quit") (equalp command "exit"))) + (game-command command player) + ;(server-send (game-command command player)) + ;(update-world) + (input-string command)) + (format t "~&Goodbye!")))) + +(defun create-player (player-name) + "The user creates a new player" + ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. + ;; Is it worth cleaning up? + (let ((player (make-player :name player-name + :place (world-starting-place *world*))) + (race NIL) (character-class NIL) + (char-attr + '((strength 0) (dexterity 0) + (constitution 0) (intelligence 0))) + (items NIL) (weapon "") + (character-points NIL)) + (format t "~&The name you have chosen is not registered on this game.") + (unless (y-or-n-p "~&Create a new player?") (start-menu)) + ;; Chose race and class + (format t "~&Please chose a race:") + (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) + (setf race (input-string)) + (while (not (member race (list-world-objects 'race) :test #'equalp)) + (format t "~&Invalid choice. Please reenter:") + (setf race (input-string))) + (setf (player-race player) (get-game-object 'race race)) + (format t "~&Please chose a class:") + (format t "~&Options: ~A" (string-from-list + (list-world-objects 'character-class))) + (setf character-class (input-string)) + (while (not (member character-class + (list-world-objects 'character-class) :test #'equalp)) + (format t "~&Invalid choice. Please reenter:") + (setf character-class (input-string))) + (setf (player-class player) + (get-game-object 'character-class character-class)) + ;; Set character attributes + (while (< (reduce #'+ character-points) 24) ; Warning: magic number! + (set-list (1+ (random 20)) a b c d) + (setf character-points (list a b c d))) + (setf text " +Now distribute your attribute points. Random numbers have been chosen, +you may assign one number to each of the following attributes:") + (format t "~&~A~%~A~%~%The numbers are:" + text (string-from-list (keys char-attr))) + ;; TODO I should replace simple-input with something offering 'magic' + (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) + (safe-nth i (keys char-attr))) + (val (cassoc attr char-attr) (cassoc attr char-attr))) + ((= i (length char-attr)) player) + (format t "~&~A" (string-from-list character-points)) + (simple-input val (concatenate 'string (symbol-name attr) ":")) + (while (not (member val character-points)) + (format t "~&Sorry, invalid number chosen. Please reenter:") + (simple-input val (concatenate 'string (symbol-name attr) ":"))) + ;; FIXME Gives problems if two equal numbers are in char-points + (let ((player-fn (build-symbol "player-" attr))) + ;; XXX Kludge ?! + (eval `(setf (,player-fn ,player) ,val))) + (setf character-points + (remove-if #'(lambda (x) (= x val)) character-points))))) + +(defun describe-place (p) + "Print out a complete description of place p" + (when (stringp p) (setf p (get-game-object 'place p))) + (format t "~&~%~A" (place-description p)) + (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) + (format t "~&Players present: ~A" (string-from-list (place-player p))) + (format t "~&Items: ~A" (string-from-list (place-item p))) + (format t "~&NPCs: ~A" (string-from-list (place-npc p))) + (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) + +(defun game-command (cmd player) + "Execute a typed-in game command" + (let* ((command (read-from-string cmd)) + (space (position #\Space cmd)) + (arg (if space (second (cut-string cmd (1+ space))) NIL))) + (if (member command *commands*) + (if space (funcall command player arg) + (funcall command player)) + (progn (format t "~&Sorry, this command does not exist!") + (format t "~&Type 'help' for a list of commands."))))) + + +;;; +;;; Here follow the functions that define the in-game commands. +;;; + + +;; A list of all in-game commands. Each new command must be registered here. +(defvar *commands* + '(help place player goto)) + +;;; The following commands don't take any arguments except for a player + +(defun help (player) + "Print out a list of in-game commands" + ;; TODO Prettify the typesetting (instead of using tabs) + (let ((tab (string #\tab))) + (format t "~&Commands:~%") + (format t "~&help~A-~AShow this list of game commands" tab tab) + (format t "~&quit/exit~A-~AExit the game" tab tab) + (format t "~&place~A-~ADescribe the current location" tab tab) + (format t "~&player~A-~ADescribe your player" tab tab) + (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) + (when (equalp (player-name player) (world-game-manager *world*)) + (format t "~&load ~A-~ALoad a saved game" tab tab) + (format t "~&save ~A-~ASave the game to file" tab tab)))) + +;; XXX Will the following two functions give problems? (Their name is +;; identical with the struct name) Probably not, but best to be aware. +(defun place (player) + "Describe the player's current location" + (describe-place (player-place player))) + +(defun player (p) + "Print a description of this player" + (when (stringp p) (setf p (get-game-object 'player p))) + (format t "~&Player ~A:" (player-name p)) + (format t "~&~%Current place: ~A" (player-place p)) + (format t "~&Race: ~A~AClass: ~A" + (race-name (player-race p)) (string #\Tab) + (character-class-name (player-class p))) + (format t "~&=====") + (format t "~&Attributes:") + (format t "~&Intelligence: ~A~AStrength: ~A" + (player-intelligence p) (string #\Tab) (player-strength p)) + (format t "~&Constitution: ~A~ADexterity: ~A" + (player-constitution p) (string #\Tab) (player-dexterity p)) + (format t "~&=====") + (format t "~&Weapon: ~A" + (if (player-weapon p) (weapon-name (player-weapon p)) "")) + (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) + +;;; These next functions have to take exactly two argument (the argument +;;; to the function and a player instance). + +(defun goto (player location) + "Go to the specified location" + (format t "~&~A is going to ~A." (player-name player) location) + (when (symbolp location) (setf location (symbol-name location))) + (when (not (member location + (place-neighbour (get-game-object 'place + (player-place player))) + :test #'equalp)) + (format t "~&This place does not border your current location!") + (return-from goto NIL)) + (remove-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (set-object-attribute player 'place location) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (describe-place location)) + diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..622f9a8 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,15 +7,14 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 2)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") (defun development () @@ -37,7 +36,7 @@ "Start a new game on a server" (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (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 ~S on port ~A" world-file port) @@ -61,6 +60,11 @@ (play-game name)) +(defun single-player () + "Start a single-player game" + ;; TODO + ) + (defun print-version () (format t "~&Lisp Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) @@ -74,23 +78,18 @@ (dolist (line (load-text-file "banner.txt")) (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") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) + (setf options '("Start a server" "Join a game" "Play single-player" + "Develop" "About" "Exit")) + (setf choice (choose-option options)) (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (0 (start-server)) + (1 (join-game)) + (2 (single-player)) + (3 (development)) + (4 (print-version) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) + (5 (format t "~&Goodbye!") + (quit)))) (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/ui.lisp b/lisp/ui.lisp new file mode 100644 index 0000000..e51c597 --- /dev/null +++ b/lisp/ui.lisp @@ -0,0 +1,188 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The client module is responsible for the actual user interface presented +;;; to a player. (Warning: this will likely change significantly, currently +;;; I am only implementing a mock-up before I get the networking part working.) +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 21/05/2015 +;;; + +(let ((player NIL)) + (defun play-game (player-name) + "The main game loop" + ;(update-world) + ;; Initialize the player if necessary + (when (null player) + (setf player (get-game-object 'player player-name))) + (when (null player) + (setf player (create-player player-name)) + (when (null (list-world-objects 'player)) + (setf (world-game-manager *world*) (player-name player))) + (add-game-object player) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player))) + ;; The actual game loop + (let ((place (get-game-object 'place (player-place player)))) + (describe-place place) + (input-string command) + (while (not (or (equalp command "quit") (equalp command "exit"))) + (game-command command player) + ;(server-send (game-command command player)) + ;(update-world) + (input-string command)) + (format t "~&Goodbye!")))) + +(defun create-player (player-name) + "The user creates a new player" + ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. + ;; Is it worth cleaning up? + (let ((player (make-player :name player-name + :place (world-starting-place *world*))) + (race NIL) (character-class NIL) + (char-attr + '((strength 0) (dexterity 0) + (constitution 0) (intelligence 0))) + (items NIL) (weapon "") + (character-points NIL)) + (format t "~&The name you have chosen is not registered on this game.") + (unless (y-or-n-p "~&Create a new player?") (start-menu)) + ;; Chose race and class + (format t "~&Please chose a race:") + (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) + (setf race (input-string)) + (while (not (member race (list-world-objects 'race) :test #'equalp)) + (format t "~&Invalid choice. Please reenter:") + (setf race (input-string))) + (setf (player-race player) (get-game-object 'race race)) + (format t "~&Please chose a class:") + (format t "~&Options: ~A" (string-from-list + (list-world-objects 'character-class))) + (setf character-class (input-string)) + (while (not (member character-class + (list-world-objects 'character-class) :test #'equalp)) + (format t "~&Invalid choice. Please reenter:") + (setf character-class (input-string))) + (setf (player-class player) + (get-game-object 'character-class character-class)) + ;; Set character attributes + (while (< (reduce #'+ character-points) 24) ; Warning: magic number! + (set-list (1+ (random 20)) a b c d) + (setf character-points (list a b c d))) + (setf text " +Now distribute your attribute points. Random numbers have been chosen, +you may assign one number to each of the following attributes:") + (format t "~&~A~%~A~%~%The numbers are:" + text (string-from-list (keys char-attr))) + ;; TODO I should replace simple-input with something offering 'magic' + (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) + (safe-nth i (keys char-attr))) + (val (cassoc attr char-attr) (cassoc attr char-attr))) + ((= i (length char-attr)) player) + (format t "~&~A" (string-from-list character-points)) + (simple-input val (concatenate 'string (symbol-name attr) ":")) + (while (not (member val character-points)) + (format t "~&Sorry, invalid number chosen. Please reenter:") + (simple-input val (concatenate 'string (symbol-name attr) ":"))) + ;; FIXME Gives problems if two equal numbers are in char-points + (let ((player-fn (build-symbol "player-" attr))) + ;; XXX Kludge ?! + (eval `(setf (,player-fn ,player) ,val))) + (setf character-points + (remove-if #'(lambda (x) (= x val)) character-points))))) + +(defun describe-place (p) + "Print out a complete description of place p" + (when (stringp p) (setf p (get-game-object 'place p))) + (format t "~&~%~A" (place-description p)) + (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) + (format t "~&Players present: ~A" (string-from-list (place-player p))) + (format t "~&Items: ~A" (string-from-list (place-item p))) + (format t "~&NPCs: ~A" (string-from-list (place-npc p))) + (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) + +(defun game-command (cmd player) + "Execute a typed-in game command" + (let* ((command (read-from-string cmd)) + (space (position #\Space cmd)) + (arg (if space (second (cut-string cmd (1+ space))) NIL))) + (if (member command *commands*) + (if space (funcall command player arg) + (funcall command player)) + (progn (format t "~&Sorry, this command does not exist!") + (format t "~&Type 'help' for a list of commands."))))) + + +;;; +;;; Here follow the functions that define the in-game commands. +;;; + + +;; A list of all in-game commands. Each new command must be registered here. +(defvar *commands* + '(help place player goto)) + +;;; The following commands don't take any arguments except for a player + +(defun help (player) + "Print out a list of in-game commands" + ;; TODO Prettify the typesetting (instead of using tabs) + (let ((tab (string #\tab))) + (format t "~&Commands:~%") + (format t "~&help~A-~AShow this list of game commands" tab tab) + (format t "~&quit/exit~A-~AExit the game" tab tab) + (format t "~&place~A-~ADescribe the current location" tab tab) + (format t "~&player~A-~ADescribe your player" tab tab) + (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) + (when (equalp (player-name player) (world-game-manager *world*)) + (format t "~&load ~A-~ALoad a saved game" tab tab) + (format t "~&save ~A-~ASave the game to file" tab tab)))) + +;; XXX Will the following two functions give problems? (Their name is +;; identical with the struct name) Probably not, but best to be aware. +(defun place (player) + "Describe the player's current location" + (describe-place (player-place player))) + +(defun player (p) + "Print a description of this player" + (when (stringp p) (setf p (get-game-object 'player p))) + (format t "~&Player ~A:" (player-name p)) + (format t "~&~%Current place: ~A" (player-place p)) + (format t "~&Race: ~A~AClass: ~A" + (race-name (player-race p)) (string #\Tab) + (character-class-name (player-class p))) + (format t "~&=====") + (format t "~&Attributes:") + (format t "~&Intelligence: ~A~AStrength: ~A" + (player-intelligence p) (string #\Tab) (player-strength p)) + (format t "~&Constitution: ~A~ADexterity: ~A" + (player-constitution p) (string #\Tab) (player-dexterity p)) + (format t "~&=====") + (format t "~&Weapon: ~A" + (if (player-weapon p) (weapon-name (player-weapon p)) "")) + (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) + +;;; These next functions have to take exactly two argument (the argument +;;; to the function and a player instance). + +(defun goto (player location) + "Go to the specified location" + (format t "~&~A is going to ~A." (player-name player) location) + (when (symbolp location) (setf location (symbol-name location))) + (when (not (member location + (place-neighbour (get-game-object 'place + (player-place player))) + :test #'equalp)) + (format t "~&This place does not border your current location!") + (return-from goto NIL)) + (remove-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (set-object-attribute player 'place location) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (describe-place location)) + diff --git a/lisp/util.lisp b/lisp/util.lisp index eeeb46d..bfd5a3f 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -27,12 +27,14 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) +;; XXX Very useful for debugging, but represents a major security hole +;; when used in a network setting (defmacro magic (var) "Execute typed-in Lisp code" `(when (eq ,var 'magic) (repl))) -; potentially inefficient if called often +;; XXX potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" (do* ((expr (list 'setf)) (vl var-list (cdr vl)) (var (car vl) (car vl))) @@ -174,6 +176,17 @@ (setf name-list (cons (funcall get-object-name object) name-list)))))) +(defun choose-option (option-list) + "The user chooses one out of a list of options, the index is returned" + (dotimes (i (length option-list)) + (format t "~&~S) ~A" (1+ i) (nth i option-list))) + (simple-input choice) + (while (or (not (numberp choice)) (< choice 1) + (> choice (length option-list))) + (format t "~&Invalid choice! Please choose again:") + (simple-input choice)) + (1- choice)) + (defun repl () "Launch a read-eval-print loop" (let ((expr (simple-input expr "lisp >")))