diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/src/server.lisp b/src/server.lisp new file mode 100644 index 0000000..fa4d470 --- /dev/null +++ b/src/server.lisp @@ -0,0 +1,163 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file stores all game data and handles the server. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX utility function during development, remove later +(defun dt (&optional (n 0)) + (bt:destroy-thread (nth n (bt:all-threads)))) + +;; TODO save and load functions +;; XXX Will probably require `make-load-form-saving-slots' + +;;; WORLD OBJECT + +(let ((world NIL)) + (defun set-world (w) (setf world w)) + + (defun world-size () (length world)) + + (defun save-topography (file-name) ;XXX (re)move this? + "Save the world topography as a text file" + (debugging "~&Saving world to file ~A" file-name) ;debug + (with-open-file (tf file-name :direction :output) + (dolist (row world) + (format stream "~&~A~%" + (string-from-list + (mapcar #'(lambda (p) + (biome-char (patch-biome p))) + row) ""))))) + + (defun save-world () + ;;TODO + ) + + (defun coord (x y) + "Return the patch at the given coordinates or NIL if out of bounds" + (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) + (nth x (nth y world))))) + +;;; WORLD THREADS + +(let ((uptime 0) (world-thread NIL) (server-thread NIL) + (player-threads NIL) (running NIL)) + + (defun start-server (&optional (force NIL)) + "Start the game server" + ;;TODO cannot restart -> ADDRESS-IN-USE ERROR + ;; -> comes from not closing connections properly? + ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? + (when force + (reset-server-threads) + (reset-world-age)) + (unless (or world-thread server-thread) + (init-world) + (setf running T) + (setf world-thread + (bt:make-thread #'update-loop :name "world-thread")) + (setf server-thread + (bt:make-thread #'run-server :name "server-thread")))) + + (defun terminate () + (notify "Terminating the world.") + (setf running NIL) + ;;XXX have to use destroy-thread because the server mostly idles, + ;; waiting for connections - only checks 'running' when connecting + (bt:destroy-thread server-thread) + (bt:join-thread world-thread) + (dolist (pt player-threads) + (bt:join-thread pt)) + (reset-server-threads) + (save-world)) ;XXX not yet implemented + + (defun age-of-the-world () uptime) + + (defun reset-world-age () (setf uptime 0)) + + (defun runningp () running) + + (defun reset-server-threads () + (set-list NIL server-thread world-thread player-threads)) + + (defun update-loop () + "The main loop, updating the world in the background" + ;;XXX split this up into two or more functions, to be run by + ;; different threads? + (logging "UPDATE ~S" uptime) + ;;Update all items and occupants in each patch + (dotimes (y (world-size)) + (dotimes (x (world-size)) + (unless running (return-from update-loop)) + (when (patch-occupant (coord x y)) + (update (patch-occupant (coord x y)))) + (dolist (i (patch-items (coord x y))) + (update i)))) + ;;Update all items each player has + ;;TODO + ;;Save the world and start over + (save-world) ;XXX not yet implemented + (incf uptime) + (sleep (/ *framerate* 1000)) + (when running (update-loop))) ;;requires Tail-Call Optimization + + (defun run-server () + "Start a server, listening for connections" + (usocket:with-socket-listener (socket "127.0.0.1" *port*) + (while running + (usocket:wait-for-input socket) + (let ((thread (bt:make-thread + #'(lambda () (handle-connection socket)) + :name (string-from-list (list "player-thread" + (length player-threads)) "-")))) + (setf player-threads (cons thread player-threads)) + (sleep 1))))) ;;give the socket a chance to connect + + (defun handle-connection (socket) + "Answer requests until the player disconnects" + (usocket:with-connected-socket (conn (usocket:socket-accept socket)) + (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' + (do* ((sockstr (usocket:socket-stream conn)) + (request (read-line sockstr NIL) (read-line sockstr NIL))) + ;;TODO remove player-thread from list when terminated + ((or (not running) (null request))) + (format sockstr "~S~%" (to-string (answer request))) + (finish-output sockstr))))) + +(defun answer (request) + (logging "SERVER: received request ~S" request) + (let* ((reqelts (extract-elements request)) + (player-name (first reqelts)) + (cmd (second reqelts)) + (args (cddr reqelts))) + (cond ((eq player-name 'ACK) "ACK ACK") ;debug + ((eq cmd 'get-map) (get-map player-name)) + ((eq cmd 'describe-patch) (describe-patch args)) + ;;TODO + ))) + +;;; COMMUNICATION FUNCTIONS + +(defun get-map (player-name) + "Return a 2d list of map places (each a list of a char and a colour)" + ;;TODO + ) + +(defun describe-patch (coords) + "Return a list of lines describing the patch at these coordinates." + (let ((p (coord (first coords) (second coords)))) + (list (string-upcase (biome-name (patch-biome p))) "" + (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" + (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) + (when (patch-occupant p) ;TODO players -> "$name is here." + (format NIL "There is ~A here." + (leading-vowel (.name (patch-occupant p))))) + (when (patch-items p) + (format NIL "The following items are here:~A *~A" #\newline + (string-from-list (mapcar #'.name (patch-items p))) + (format NIL "~% *")))))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/src/server.lisp b/src/server.lisp new file mode 100644 index 0000000..fa4d470 --- /dev/null +++ b/src/server.lisp @@ -0,0 +1,163 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file stores all game data and handles the server. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX utility function during development, remove later +(defun dt (&optional (n 0)) + (bt:destroy-thread (nth n (bt:all-threads)))) + +;; TODO save and load functions +;; XXX Will probably require `make-load-form-saving-slots' + +;;; WORLD OBJECT + +(let ((world NIL)) + (defun set-world (w) (setf world w)) + + (defun world-size () (length world)) + + (defun save-topography (file-name) ;XXX (re)move this? + "Save the world topography as a text file" + (debugging "~&Saving world to file ~A" file-name) ;debug + (with-open-file (tf file-name :direction :output) + (dolist (row world) + (format stream "~&~A~%" + (string-from-list + (mapcar #'(lambda (p) + (biome-char (patch-biome p))) + row) ""))))) + + (defun save-world () + ;;TODO + ) + + (defun coord (x y) + "Return the patch at the given coordinates or NIL if out of bounds" + (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) + (nth x (nth y world))))) + +;;; WORLD THREADS + +(let ((uptime 0) (world-thread NIL) (server-thread NIL) + (player-threads NIL) (running NIL)) + + (defun start-server (&optional (force NIL)) + "Start the game server" + ;;TODO cannot restart -> ADDRESS-IN-USE ERROR + ;; -> comes from not closing connections properly? + ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? + (when force + (reset-server-threads) + (reset-world-age)) + (unless (or world-thread server-thread) + (init-world) + (setf running T) + (setf world-thread + (bt:make-thread #'update-loop :name "world-thread")) + (setf server-thread + (bt:make-thread #'run-server :name "server-thread")))) + + (defun terminate () + (notify "Terminating the world.") + (setf running NIL) + ;;XXX have to use destroy-thread because the server mostly idles, + ;; waiting for connections - only checks 'running' when connecting + (bt:destroy-thread server-thread) + (bt:join-thread world-thread) + (dolist (pt player-threads) + (bt:join-thread pt)) + (reset-server-threads) + (save-world)) ;XXX not yet implemented + + (defun age-of-the-world () uptime) + + (defun reset-world-age () (setf uptime 0)) + + (defun runningp () running) + + (defun reset-server-threads () + (set-list NIL server-thread world-thread player-threads)) + + (defun update-loop () + "The main loop, updating the world in the background" + ;;XXX split this up into two or more functions, to be run by + ;; different threads? + (logging "UPDATE ~S" uptime) + ;;Update all items and occupants in each patch + (dotimes (y (world-size)) + (dotimes (x (world-size)) + (unless running (return-from update-loop)) + (when (patch-occupant (coord x y)) + (update (patch-occupant (coord x y)))) + (dolist (i (patch-items (coord x y))) + (update i)))) + ;;Update all items each player has + ;;TODO + ;;Save the world and start over + (save-world) ;XXX not yet implemented + (incf uptime) + (sleep (/ *framerate* 1000)) + (when running (update-loop))) ;;requires Tail-Call Optimization + + (defun run-server () + "Start a server, listening for connections" + (usocket:with-socket-listener (socket "127.0.0.1" *port*) + (while running + (usocket:wait-for-input socket) + (let ((thread (bt:make-thread + #'(lambda () (handle-connection socket)) + :name (string-from-list (list "player-thread" + (length player-threads)) "-")))) + (setf player-threads (cons thread player-threads)) + (sleep 1))))) ;;give the socket a chance to connect + + (defun handle-connection (socket) + "Answer requests until the player disconnects" + (usocket:with-connected-socket (conn (usocket:socket-accept socket)) + (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' + (do* ((sockstr (usocket:socket-stream conn)) + (request (read-line sockstr NIL) (read-line sockstr NIL))) + ;;TODO remove player-thread from list when terminated + ((or (not running) (null request))) + (format sockstr "~S~%" (to-string (answer request))) + (finish-output sockstr))))) + +(defun answer (request) + (logging "SERVER: received request ~S" request) + (let* ((reqelts (extract-elements request)) + (player-name (first reqelts)) + (cmd (second reqelts)) + (args (cddr reqelts))) + (cond ((eq player-name 'ACK) "ACK ACK") ;debug + ((eq cmd 'get-map) (get-map player-name)) + ((eq cmd 'describe-patch) (describe-patch args)) + ;;TODO + ))) + +;;; COMMUNICATION FUNCTIONS + +(defun get-map (player-name) + "Return a 2d list of map places (each a list of a char and a colour)" + ;;TODO + ) + +(defun describe-patch (coords) + "Return a list of lines describing the patch at these coordinates." + (let ((p (coord (first coords) (second coords)))) + (list (string-upcase (biome-name (patch-biome p))) "" + (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" + (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) + (when (patch-occupant p) ;TODO players -> "$name is here." + (format NIL "There is ~A here." + (leading-vowel (.name (patch-occupant p))))) + (when (patch-items p) + (format NIL "The following items are here:~A *~A" #\newline + (string-from-list (mapcar #'.name (patch-items p))) + (format NIL "~% *")))))) diff --git a/src/util.lisp b/src/util.lisp new file mode 100644 index 0000000..341e15c --- /dev/null +++ b/src/util.lisp @@ -0,0 +1,339 @@ +;;; +;;; This is a personal utility module that I use in most of my Common Lisp +;;; projects. It originated in the Atlantis project, but has been used many +;;; times since and evolved accordingly. +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(in-package :naledi-ya-africa) ;;XXX change to dv-utils? + +;;; MACROS + +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + +(defmacro logging (str &rest format-args) + "Write an entry to *logfile*" + ;;FIXME gives error if cwd != file directory + `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) + +(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))) + ((null vl) expr) + (setf (cdr (last expr)) (list var)) + (setf (cdr (last expr)) (list value)))) + +(defmacro input (&rest vars) + "Take input from terminal and store each element in a passed variable" + ;; XXX Add a prompt parameter again? + `(progn + (format t "~&>>> ") + (set-list (read) ,@vars) + (first (list ,@vars)))) + +(defmacro input-string (&optional (var (gensym))) + "Read a string input line" + `(progn + (format t "~&>>> ") + (setf ,var (read-line)) + ,var)) + +(defmacro while (condition &body body) + "An implementation of a while loop as found in other languages" + `(do () + ((not ,condition) NIL) + ,@body)) + +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) + +(defmacro cassoc (entry table &key (test '#'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-nth (index lst) + "Return (nth index lst), or NIL if index is out of range" + `(if (> ,index (1- (length ,lst))) + NIL (nth ,index ,lst))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + +;;; FUNCTIONS + +;; Some of these functions are probably quite inefficient (lots of consing) + +(defun remove-first-if (fn lst) + ;;FIXME isn't this identical to `remove-if'? + "Remove the first element in a list that satisfies the given predicate" + (cond ((null lst) NIL) + ((funcall fn (car lst)) (cdr lst)) + (T (cons (car lst) (remove-first-if fn (cdr lst)))))) + +(defun average (&rest numbers) + "Compute the average of the given numbers" + (/ (reduce #'+ numbers) (length numbers))) + +(defun halve (n &optional (round-fn 'round)) + "Halve a given number and round it to an integer." + (let ((half (/ n 2))) + (cond ((eq round-fn 'up) (ceiling half)) + ((eq round-fn 'down) (floor half)) + (T (round half))))) + +(defun keys (assoc-list) + "Return a list of the keys in an association list" + (if (null assoc-list) NIL + (cons (car (car assoc-list)) (keys (cdr assoc-list))))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components :sep ""))) + +(defun symbol-to-string (sym) + "Convert a symbol to a string, exchanging dashes for spaces" + (string-from-list + (split-string (string-downcase (string sym)) #\-) #\space)) + +(defun letter-in-string (letter str) + "Is this letter in this string?" + (dotimes (i (length str) NIL) + (when (equalp (aref str i) letter) (return T)))) + +;; TODO change &optional to &key (and figure out why the heck that doesn't +;; work - clisp bug?), add null-filler keyword +(defun string-from-list (lst &optional (separator #\space)) + "Put all elements of lst into a single string, separated by the separator" + (cond ((null lst) "") + ((= (length lst) 1) (to-string (car lst))) + (T (concatenate 'string (to-string (first lst)) (to-string separator) + (string-from-list (cdr lst) separator))))) + +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (if (or (minusp i) (> i (length s))) s + (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) + (dotimes (c (length s) (list s1 s2)) + (if (> i c) + (setf (aref s1 c) (aref s c)) + (setf (aref s2 (- c i)) (aref s c))))))) + +(defun char-list-to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun trim-whitespace (s) + "Trim off spaces and tabs before and after string s" + (string-trim '(#\space #\tab) s)) + +(defun to-string (x) + "Whatever x is, convert it into a string" + (cond ((stringp x) x) + ((or (symbolp x) (characterp x)) (string x)) + (t (format NIL "~S" x)))) + +(defun leading-vowel (noun) + "Return noun prepended with 'a' or 'an', depending on its first letter." + (format NIL "~A ~A" + (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") + noun)) + +(defun break-lines (lines width &optional (indent "")) + "Take a list of lines and break any that are too long." + (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) + ((null ls) result) + (if (<= (length l) width) + (setf result (append result (list l))) + (setf ls (append (list nil (first (cut-string l width)) + (concatenate 'string (to-string indent) + (second (cut-string l width)))) + (cdr ls)))))) + +(defun extract-elements (str) + "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" + (multiple-value-bind (next-element i) (read-from-string str nil) + (if (null next-element) NIL + (cons next-element + (extract-elements (second (cut-string str i))))))) + +(defun count-instances (search-term search-sequence &key (test #'eql)) + "Count the number of instances of search-term in search-sequence" + (let ((count 0)) + (dotimes (i (length search-sequence) count) + (when (funcall test search-term (elt search-sequence i)) + (incf count))))) + +(defun most-common-element (lst &key (test #'eql)) + "Return the most common element in this list and how often it appears" + ;;This function has multiple return values! + ;;In case of multiple mces, return the one that appears first + (let ((elements-counted NIL) (max 0) (mce NIL)) + (dolist (e lst (values mce max)) + (unless (member e elements-counted :test test) + (let ((count (count-instances e lst :test test))) + (when (> count max) + (setf max count) + (setf mce e))) + (setf elements-counted (append elements-counted (list e))))))) + +(defun nths (n lst) + "Take in a list of lists and return the nth element of each" + (when (and lst (listp (car lst))) + (cons (nth n (car lst)) (nths n (cdr lst))))) + +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + +(defun range (stop &key (start 0) (step 1)) + "Return a list of numbers from start to stop" + ;;XXX Surely this must exist as a function in Common Lisp already, + ;; I just don't know what it's called... + (unless (>= start stop) + (cons start (range stop :start (+ start step) :step step)))) + +(defun to-list (vector &optional (next-elt 0)) + "Turn the vector into a list" + (if (= next-elt (1- (length vector))) NIL + (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) + +(defun cut-list (l i) + "Cut list l in two at index i and return the two sublists in a list" + (if (or (< i 1) (> i (length l))) l + (do* ((lst2 l (cdr lst2)) + (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) + ((= i (length lst1)) (list lst1 (cdr lst2)))))) + +(defun random-elt (seq) + "Return a random element of this sequence" + (elt seq (random (length seq)))) + +(defun random-offset (n max-offset) + "Return n plus a random offset" + (+ n (- (random (* 2 max-offset)) max-offset))) + +(defun chancep (p) + "Do a random test with 1/p probability of success." + (= 1 (random p))) + +(defun probabilityp (percent) + "Do a random test, with the percentage giving the success probability" + ;;Accuracy: 0.1 + (> percent (/ (random 1000) 10))) + +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + (with-open-file (f file-name) + (do* ((line (read-line f nil nil) + (read-line f nil nil)) + (file-lines (list line) (append file-lines (list line)))) + ((null line) file-lines)))) + +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + +(defun write-to-file (text filename &optional (append NIL)) + "Write text (a string or list of strings) to the specified file" + (let ((text-list (if (listp text) text (list text))) + (f (if append + (open filename :direction :output + :if-exists :append :if-does-not-exist :create) + (open filename :direction :output :if-exists :supersede)))) + (dolist (line text-list) + (format f "~&~A~&" line)) + (close f))) + +(defun write-list (lst filename) + "Write a list to file, one entry per line." + (let ((f (open filename :direction :output))) + (dolist (i lst) (format f "~&~S" i)) + (close f))) + +(defun time-stamp (&optional (time-t (get-universal-time))) + (let ((time (multiple-value-list (decode-universal-time time-t)))) + (format NIL "~S/~S/~S ~S:~S" + (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components ""))) + +(defun make-list-function (container-type &optional (add-s t)) + "Return a function to return a list of the names of all objects of the +specified type in the container struct" + #'(lambda (object-type container) + (let* ((get-objects (symbol-function + (build-symbol container-type "-" + object-type (if add-s "s" "")))) + (get-object-name (symbol-function + (build-symbol object-type "-name"))) + (objects (funcall get-objects container)) (name-list NIL)) + (dolist (o objects name-list) + (when (stringp o) (return objects)) + (setf name-list + (cons (funcall get-object-name o) name-list)))))) + +;; DEPRECATED - replace with number-menu +(defun choose-number-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)) + +;; DEPRECATED - replace with number-menu +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +;; SUPERSEDES choose-number-option AND choose-option! +(defun number-menu (entries &optional (header "Please choose an option:")) + "Display a number-based menu and act on the user's choice" + ;; The param entries is a list of lists in the form + ;; ( ) + (format t "~&~A" header) + (dotimes (i (length entries)) + (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) + (format t "~&>>> ") + (setf choice (read)) + (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) + (format t "~&Invalid choice! Please choose again:~%>>> ") + (setf choice (read))) + (funcall (second (nth choice entries)))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/src/server.lisp b/src/server.lisp new file mode 100644 index 0000000..fa4d470 --- /dev/null +++ b/src/server.lisp @@ -0,0 +1,163 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file stores all game data and handles the server. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX utility function during development, remove later +(defun dt (&optional (n 0)) + (bt:destroy-thread (nth n (bt:all-threads)))) + +;; TODO save and load functions +;; XXX Will probably require `make-load-form-saving-slots' + +;;; WORLD OBJECT + +(let ((world NIL)) + (defun set-world (w) (setf world w)) + + (defun world-size () (length world)) + + (defun save-topography (file-name) ;XXX (re)move this? + "Save the world topography as a text file" + (debugging "~&Saving world to file ~A" file-name) ;debug + (with-open-file (tf file-name :direction :output) + (dolist (row world) + (format stream "~&~A~%" + (string-from-list + (mapcar #'(lambda (p) + (biome-char (patch-biome p))) + row) ""))))) + + (defun save-world () + ;;TODO + ) + + (defun coord (x y) + "Return the patch at the given coordinates or NIL if out of bounds" + (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) + (nth x (nth y world))))) + +;;; WORLD THREADS + +(let ((uptime 0) (world-thread NIL) (server-thread NIL) + (player-threads NIL) (running NIL)) + + (defun start-server (&optional (force NIL)) + "Start the game server" + ;;TODO cannot restart -> ADDRESS-IN-USE ERROR + ;; -> comes from not closing connections properly? + ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? + (when force + (reset-server-threads) + (reset-world-age)) + (unless (or world-thread server-thread) + (init-world) + (setf running T) + (setf world-thread + (bt:make-thread #'update-loop :name "world-thread")) + (setf server-thread + (bt:make-thread #'run-server :name "server-thread")))) + + (defun terminate () + (notify "Terminating the world.") + (setf running NIL) + ;;XXX have to use destroy-thread because the server mostly idles, + ;; waiting for connections - only checks 'running' when connecting + (bt:destroy-thread server-thread) + (bt:join-thread world-thread) + (dolist (pt player-threads) + (bt:join-thread pt)) + (reset-server-threads) + (save-world)) ;XXX not yet implemented + + (defun age-of-the-world () uptime) + + (defun reset-world-age () (setf uptime 0)) + + (defun runningp () running) + + (defun reset-server-threads () + (set-list NIL server-thread world-thread player-threads)) + + (defun update-loop () + "The main loop, updating the world in the background" + ;;XXX split this up into two or more functions, to be run by + ;; different threads? + (logging "UPDATE ~S" uptime) + ;;Update all items and occupants in each patch + (dotimes (y (world-size)) + (dotimes (x (world-size)) + (unless running (return-from update-loop)) + (when (patch-occupant (coord x y)) + (update (patch-occupant (coord x y)))) + (dolist (i (patch-items (coord x y))) + (update i)))) + ;;Update all items each player has + ;;TODO + ;;Save the world and start over + (save-world) ;XXX not yet implemented + (incf uptime) + (sleep (/ *framerate* 1000)) + (when running (update-loop))) ;;requires Tail-Call Optimization + + (defun run-server () + "Start a server, listening for connections" + (usocket:with-socket-listener (socket "127.0.0.1" *port*) + (while running + (usocket:wait-for-input socket) + (let ((thread (bt:make-thread + #'(lambda () (handle-connection socket)) + :name (string-from-list (list "player-thread" + (length player-threads)) "-")))) + (setf player-threads (cons thread player-threads)) + (sleep 1))))) ;;give the socket a chance to connect + + (defun handle-connection (socket) + "Answer requests until the player disconnects" + (usocket:with-connected-socket (conn (usocket:socket-accept socket)) + (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' + (do* ((sockstr (usocket:socket-stream conn)) + (request (read-line sockstr NIL) (read-line sockstr NIL))) + ;;TODO remove player-thread from list when terminated + ((or (not running) (null request))) + (format sockstr "~S~%" (to-string (answer request))) + (finish-output sockstr))))) + +(defun answer (request) + (logging "SERVER: received request ~S" request) + (let* ((reqelts (extract-elements request)) + (player-name (first reqelts)) + (cmd (second reqelts)) + (args (cddr reqelts))) + (cond ((eq player-name 'ACK) "ACK ACK") ;debug + ((eq cmd 'get-map) (get-map player-name)) + ((eq cmd 'describe-patch) (describe-patch args)) + ;;TODO + ))) + +;;; COMMUNICATION FUNCTIONS + +(defun get-map (player-name) + "Return a 2d list of map places (each a list of a char and a colour)" + ;;TODO + ) + +(defun describe-patch (coords) + "Return a list of lines describing the patch at these coordinates." + (let ((p (coord (first coords) (second coords)))) + (list (string-upcase (biome-name (patch-biome p))) "" + (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" + (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) + (when (patch-occupant p) ;TODO players -> "$name is here." + (format NIL "There is ~A here." + (leading-vowel (.name (patch-occupant p))))) + (when (patch-items p) + (format NIL "The following items are here:~A *~A" #\newline + (string-from-list (mapcar #'.name (patch-items p))) + (format NIL "~% *")))))) diff --git a/src/util.lisp b/src/util.lisp new file mode 100644 index 0000000..341e15c --- /dev/null +++ b/src/util.lisp @@ -0,0 +1,339 @@ +;;; +;;; This is a personal utility module that I use in most of my Common Lisp +;;; projects. It originated in the Atlantis project, but has been used many +;;; times since and evolved accordingly. +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(in-package :naledi-ya-africa) ;;XXX change to dv-utils? + +;;; MACROS + +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + +(defmacro logging (str &rest format-args) + "Write an entry to *logfile*" + ;;FIXME gives error if cwd != file directory + `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) + +(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))) + ((null vl) expr) + (setf (cdr (last expr)) (list var)) + (setf (cdr (last expr)) (list value)))) + +(defmacro input (&rest vars) + "Take input from terminal and store each element in a passed variable" + ;; XXX Add a prompt parameter again? + `(progn + (format t "~&>>> ") + (set-list (read) ,@vars) + (first (list ,@vars)))) + +(defmacro input-string (&optional (var (gensym))) + "Read a string input line" + `(progn + (format t "~&>>> ") + (setf ,var (read-line)) + ,var)) + +(defmacro while (condition &body body) + "An implementation of a while loop as found in other languages" + `(do () + ((not ,condition) NIL) + ,@body)) + +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) + +(defmacro cassoc (entry table &key (test '#'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-nth (index lst) + "Return (nth index lst), or NIL if index is out of range" + `(if (> ,index (1- (length ,lst))) + NIL (nth ,index ,lst))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + +;;; FUNCTIONS + +;; Some of these functions are probably quite inefficient (lots of consing) + +(defun remove-first-if (fn lst) + ;;FIXME isn't this identical to `remove-if'? + "Remove the first element in a list that satisfies the given predicate" + (cond ((null lst) NIL) + ((funcall fn (car lst)) (cdr lst)) + (T (cons (car lst) (remove-first-if fn (cdr lst)))))) + +(defun average (&rest numbers) + "Compute the average of the given numbers" + (/ (reduce #'+ numbers) (length numbers))) + +(defun halve (n &optional (round-fn 'round)) + "Halve a given number and round it to an integer." + (let ((half (/ n 2))) + (cond ((eq round-fn 'up) (ceiling half)) + ((eq round-fn 'down) (floor half)) + (T (round half))))) + +(defun keys (assoc-list) + "Return a list of the keys in an association list" + (if (null assoc-list) NIL + (cons (car (car assoc-list)) (keys (cdr assoc-list))))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components :sep ""))) + +(defun symbol-to-string (sym) + "Convert a symbol to a string, exchanging dashes for spaces" + (string-from-list + (split-string (string-downcase (string sym)) #\-) #\space)) + +(defun letter-in-string (letter str) + "Is this letter in this string?" + (dotimes (i (length str) NIL) + (when (equalp (aref str i) letter) (return T)))) + +;; TODO change &optional to &key (and figure out why the heck that doesn't +;; work - clisp bug?), add null-filler keyword +(defun string-from-list (lst &optional (separator #\space)) + "Put all elements of lst into a single string, separated by the separator" + (cond ((null lst) "") + ((= (length lst) 1) (to-string (car lst))) + (T (concatenate 'string (to-string (first lst)) (to-string separator) + (string-from-list (cdr lst) separator))))) + +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (if (or (minusp i) (> i (length s))) s + (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) + (dotimes (c (length s) (list s1 s2)) + (if (> i c) + (setf (aref s1 c) (aref s c)) + (setf (aref s2 (- c i)) (aref s c))))))) + +(defun char-list-to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun trim-whitespace (s) + "Trim off spaces and tabs before and after string s" + (string-trim '(#\space #\tab) s)) + +(defun to-string (x) + "Whatever x is, convert it into a string" + (cond ((stringp x) x) + ((or (symbolp x) (characterp x)) (string x)) + (t (format NIL "~S" x)))) + +(defun leading-vowel (noun) + "Return noun prepended with 'a' or 'an', depending on its first letter." + (format NIL "~A ~A" + (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") + noun)) + +(defun break-lines (lines width &optional (indent "")) + "Take a list of lines and break any that are too long." + (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) + ((null ls) result) + (if (<= (length l) width) + (setf result (append result (list l))) + (setf ls (append (list nil (first (cut-string l width)) + (concatenate 'string (to-string indent) + (second (cut-string l width)))) + (cdr ls)))))) + +(defun extract-elements (str) + "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" + (multiple-value-bind (next-element i) (read-from-string str nil) + (if (null next-element) NIL + (cons next-element + (extract-elements (second (cut-string str i))))))) + +(defun count-instances (search-term search-sequence &key (test #'eql)) + "Count the number of instances of search-term in search-sequence" + (let ((count 0)) + (dotimes (i (length search-sequence) count) + (when (funcall test search-term (elt search-sequence i)) + (incf count))))) + +(defun most-common-element (lst &key (test #'eql)) + "Return the most common element in this list and how often it appears" + ;;This function has multiple return values! + ;;In case of multiple mces, return the one that appears first + (let ((elements-counted NIL) (max 0) (mce NIL)) + (dolist (e lst (values mce max)) + (unless (member e elements-counted :test test) + (let ((count (count-instances e lst :test test))) + (when (> count max) + (setf max count) + (setf mce e))) + (setf elements-counted (append elements-counted (list e))))))) + +(defun nths (n lst) + "Take in a list of lists and return the nth element of each" + (when (and lst (listp (car lst))) + (cons (nth n (car lst)) (nths n (cdr lst))))) + +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + +(defun range (stop &key (start 0) (step 1)) + "Return a list of numbers from start to stop" + ;;XXX Surely this must exist as a function in Common Lisp already, + ;; I just don't know what it's called... + (unless (>= start stop) + (cons start (range stop :start (+ start step) :step step)))) + +(defun to-list (vector &optional (next-elt 0)) + "Turn the vector into a list" + (if (= next-elt (1- (length vector))) NIL + (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) + +(defun cut-list (l i) + "Cut list l in two at index i and return the two sublists in a list" + (if (or (< i 1) (> i (length l))) l + (do* ((lst2 l (cdr lst2)) + (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) + ((= i (length lst1)) (list lst1 (cdr lst2)))))) + +(defun random-elt (seq) + "Return a random element of this sequence" + (elt seq (random (length seq)))) + +(defun random-offset (n max-offset) + "Return n plus a random offset" + (+ n (- (random (* 2 max-offset)) max-offset))) + +(defun chancep (p) + "Do a random test with 1/p probability of success." + (= 1 (random p))) + +(defun probabilityp (percent) + "Do a random test, with the percentage giving the success probability" + ;;Accuracy: 0.1 + (> percent (/ (random 1000) 10))) + +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + (with-open-file (f file-name) + (do* ((line (read-line f nil nil) + (read-line f nil nil)) + (file-lines (list line) (append file-lines (list line)))) + ((null line) file-lines)))) + +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + +(defun write-to-file (text filename &optional (append NIL)) + "Write text (a string or list of strings) to the specified file" + (let ((text-list (if (listp text) text (list text))) + (f (if append + (open filename :direction :output + :if-exists :append :if-does-not-exist :create) + (open filename :direction :output :if-exists :supersede)))) + (dolist (line text-list) + (format f "~&~A~&" line)) + (close f))) + +(defun write-list (lst filename) + "Write a list to file, one entry per line." + (let ((f (open filename :direction :output))) + (dolist (i lst) (format f "~&~S" i)) + (close f))) + +(defun time-stamp (&optional (time-t (get-universal-time))) + (let ((time (multiple-value-list (decode-universal-time time-t)))) + (format NIL "~S/~S/~S ~S:~S" + (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components ""))) + +(defun make-list-function (container-type &optional (add-s t)) + "Return a function to return a list of the names of all objects of the +specified type in the container struct" + #'(lambda (object-type container) + (let* ((get-objects (symbol-function + (build-symbol container-type "-" + object-type (if add-s "s" "")))) + (get-object-name (symbol-function + (build-symbol object-type "-name"))) + (objects (funcall get-objects container)) (name-list NIL)) + (dolist (o objects name-list) + (when (stringp o) (return objects)) + (setf name-list + (cons (funcall get-object-name o) name-list)))))) + +;; DEPRECATED - replace with number-menu +(defun choose-number-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)) + +;; DEPRECATED - replace with number-menu +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +;; SUPERSEDES choose-number-option AND choose-option! +(defun number-menu (entries &optional (header "Please choose an option:")) + "Display a number-based menu and act on the user's choice" + ;; The param entries is a list of lists in the form + ;; ( ) + (format t "~&~A" header) + (dotimes (i (length entries)) + (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) + (format t "~&>>> ") + (setf choice (read)) + (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) + (format t "~&Invalid choice! Please choose again:~%>>> ") + (setf choice (read))) + (funcall (second (nth choice entries)))) diff --git a/src/world.lisp b/src/world.lisp new file mode 100644 index 0000000..5365554 --- /dev/null +++ b/src/world.lisp @@ -0,0 +1,187 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines patches and administrates the world object. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defstruct patch + (pos '(0 0)) ;position + (biome NIL) + (items '()) + (occupant NIL)) + +(defstruct biome + (name "") + (ground "") + (features '()) ;an alist of possible features and their 1/p probabilities + (char #\.) ;default map display character + (col ':white)) ;default map display colour + +;; BIOME LIST + +(let ((biome-list NIL)) + (defun register-biome (symbol-name biome-object) + (setf biome-list (cons (list symbol-name biome-object) biome-list))) + + (defun available-biomes () + (keys biome-list)) + + (defun get-biome (symbol-name) + (cassoc symbol-name biome-list))) + +(defmacro new-biome (name &body body) + `(register-biome ',name + (make-biome + :name ,(symbol-to-string name) + ,@body))) + +;; MATRIX FUNCTIONS + +(defun init-matrix (size) + "Create a square matrix of empty patches" + ;;TODO change this to arrays for performance + (debugging "~&Creating a ~S/~S matrix." size size) + (do ((y 0 (1+ y)) (world NIL) (row NIL NIL)) + ((= y size) world) + (dotimes (x size) + (setf row (append row (list (make-patch :pos (list x y)))))) + (setf world (append world (list row))))) + +(defun distance (x1 y1 x2 y2 &optional (pythag NIL)) + "Find the distance between two sets of coordinates" + (if pythag (round (sqrt (+ (expt (- x1 x2) 2) (expt (- y1 y2) 2)))) + (min (abs (- x1 x2)) (abs (- y1 y2))))) + +(defun closest-coords (here coord-list &optional (abs-dist NIL)) + "Find the closest position to 'here' from a list of coordinates" + (do* ((clist coord-list (cdr clist)) (c (car clist) (car clist)) + (dist (when c (distance (first here) (second here) + (first c) (second c) abs-dist)) + (when c (distance (first here) (second here) + (first c) (second c) abs-dist))) + (mindist dist) (closest c)) + ((null clist) closest) + (when (< dist mindist) + (setf mindist dist closest c)))) + +(defun opposite-dir (dir) + "Return the direction opposite the input" + (let ((pos (position dir *directions*))) + (when pos (nth (rem (+ 4 pos) 8) *directions*)))) + +(defun next-dir (dir &optional (cw T)) + "Get the neighbouring direction (clockwise or anticlockwise)" + (let ((pos (position dir *directions*)) + (diff (if cw 1 -1))) + (when pos (nth (rem (+ diff pos 8) 8) *directions*)))) + +(defun orth-dir (dir &optional (cw T)) + "Get the direction orthogonal (at right angles) to the given one." + (next-dir (next-dir dir cw) cw)) + +(defun diagonalp (dir) + "Is dir a diagonal direction?" + (member dir '(NE SE SW NW) :test #'eq)) + +(defun dir2patch (herex herey therex therey) + "Calculate the direction to a patch" + (cond ((> herex therex) + (cond ((> herey therey) 'NW) + ((< herey therey) 'SW) + (T 'W))) + ((< herex therex) + (cond ((> herey therey) 'NE) + ((< herey therey) 'SE) + (T 'E))) + (T (cond ((> herey therey) 'N) + ((< herey therey) 'S) + (T NIL))))) + +(defun coordsindir (x y dir) + "Return the coordinates in the given direction" + (cond ((eq dir 'N) (list x (1- y))) + ((eq dir 'NE) (list (1+ x) (1- y))) + ((eq dir 'E) (list (1+ x) y)) + ((eq dir 'SE) (list (1+ x) (1+ y))) + ((eq dir 'S) (list x (1+ y))) + ((eq dir 'SW) (list (1- x) (1+ y))) + ((eq dir 'W) (list (1- x) y)) + ((eq dir 'NW) (list (1- x) (1- y))) + ((null dir) (list x y)) + (T (error "~&Invalid direction ~S")))) + +(defun patchindir (x y dir) + "Return the patch in the given direction" + (let* ((coords (coordsindir x y dir)) + (nextx (first coords)) (nexty (second coords))) + (coord nextx nexty))) + +(defun neighbour (p dir) + "Return the neighbouring patch in this direction" + (patchindir (first (patch-pos p)) (second (patch-pos p)) dir)) + + +;; WORLD CREATION FUNCTIONS + +(defun get-patch-feature (patch) + "Find a random feature (or none) to occupy this patch." + (let ((flist (biome-features (patch-biome patch)))) + (dolist (f flist NIL) + (when (chancep (second f)) + (return-from get-patch-feature + (make-instance (first f) :x (first (patch-pos patch)) + :y (second (patch-pos patch)))))))) + +(defun generate-biomes (size-factor) + ;;XXX The maps this produces don't look quite as expected, but for + ;; current purposes they are good enough + (debugging "~&Generating biomes") ;debug + (let* ((wsize (world-size)) (seeds NIL) + (nseeds (round (/ wsize size-factor))) + (biomes (remove-first-if + #'(lambda (e) (eq e 'stream)) + (available-biomes)))) + ;;Initialize a set of biome 'seed' coordinates + (dotimes (n nseeds) + (setf seeds + (cons (list (random wsize) + (random wsize) + (random-elt biomes)) + seeds))) + (debugging "~&~S" seeds) + ;;For each patch, calculate the closest seed and set to that biome + (dotimes (x wsize seeds) + (dotimes (y wsize) + (let ((p (coord x y)) + (b (third (closest-coords (list x y) seeds T)))) + (setf (patch-biome p) (get-biome b)) + (setf (patch-occupant p) (get-patch-feature p))))))) + +(defun generate-stream (x0 y0) + (debugging "~&Generating a stream, starting at ~S/~S" x0 y0) ;debug + (do* ((dir (random-elt *directions*) + (if (probabilityp 75) dir (next-dir dir (random-elt '(T NIL))))) + (patch (coord x0 y0) (neighbour patch dir))) + ((or (null patch) (eq (patch-biome patch) (get-biome 'stream)))) + (setf (patch-biome patch) (get-biome 'stream)) + (setf (patch-occupant patch) NIL))) + +(defun create-world (size) + "Create a world of the specified size (square)" + (set-world (init-matrix size)) + ;;XXX magic numbers + (generate-biomes 10) + (dotimes (s (round (/ (expt size 2) 2000))) + (generate-stream (random size) (random size)))) + +(defun init-world () + "Initialize the log, RNG, and world." + (write-to-file "NALEDI ya AFRICA" *logfile*) + (logging (time-stamp)) + (setf *random-state* (make-random-state t)) + (create-world *world-size*)) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/src/server.lisp b/src/server.lisp new file mode 100644 index 0000000..fa4d470 --- /dev/null +++ b/src/server.lisp @@ -0,0 +1,163 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file stores all game data and handles the server. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX utility function during development, remove later +(defun dt (&optional (n 0)) + (bt:destroy-thread (nth n (bt:all-threads)))) + +;; TODO save and load functions +;; XXX Will probably require `make-load-form-saving-slots' + +;;; WORLD OBJECT + +(let ((world NIL)) + (defun set-world (w) (setf world w)) + + (defun world-size () (length world)) + + (defun save-topography (file-name) ;XXX (re)move this? + "Save the world topography as a text file" + (debugging "~&Saving world to file ~A" file-name) ;debug + (with-open-file (tf file-name :direction :output) + (dolist (row world) + (format stream "~&~A~%" + (string-from-list + (mapcar #'(lambda (p) + (biome-char (patch-biome p))) + row) ""))))) + + (defun save-world () + ;;TODO + ) + + (defun coord (x y) + "Return the patch at the given coordinates or NIL if out of bounds" + (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) + (nth x (nth y world))))) + +;;; WORLD THREADS + +(let ((uptime 0) (world-thread NIL) (server-thread NIL) + (player-threads NIL) (running NIL)) + + (defun start-server (&optional (force NIL)) + "Start the game server" + ;;TODO cannot restart -> ADDRESS-IN-USE ERROR + ;; -> comes from not closing connections properly? + ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? + (when force + (reset-server-threads) + (reset-world-age)) + (unless (or world-thread server-thread) + (init-world) + (setf running T) + (setf world-thread + (bt:make-thread #'update-loop :name "world-thread")) + (setf server-thread + (bt:make-thread #'run-server :name "server-thread")))) + + (defun terminate () + (notify "Terminating the world.") + (setf running NIL) + ;;XXX have to use destroy-thread because the server mostly idles, + ;; waiting for connections - only checks 'running' when connecting + (bt:destroy-thread server-thread) + (bt:join-thread world-thread) + (dolist (pt player-threads) + (bt:join-thread pt)) + (reset-server-threads) + (save-world)) ;XXX not yet implemented + + (defun age-of-the-world () uptime) + + (defun reset-world-age () (setf uptime 0)) + + (defun runningp () running) + + (defun reset-server-threads () + (set-list NIL server-thread world-thread player-threads)) + + (defun update-loop () + "The main loop, updating the world in the background" + ;;XXX split this up into two or more functions, to be run by + ;; different threads? + (logging "UPDATE ~S" uptime) + ;;Update all items and occupants in each patch + (dotimes (y (world-size)) + (dotimes (x (world-size)) + (unless running (return-from update-loop)) + (when (patch-occupant (coord x y)) + (update (patch-occupant (coord x y)))) + (dolist (i (patch-items (coord x y))) + (update i)))) + ;;Update all items each player has + ;;TODO + ;;Save the world and start over + (save-world) ;XXX not yet implemented + (incf uptime) + (sleep (/ *framerate* 1000)) + (when running (update-loop))) ;;requires Tail-Call Optimization + + (defun run-server () + "Start a server, listening for connections" + (usocket:with-socket-listener (socket "127.0.0.1" *port*) + (while running + (usocket:wait-for-input socket) + (let ((thread (bt:make-thread + #'(lambda () (handle-connection socket)) + :name (string-from-list (list "player-thread" + (length player-threads)) "-")))) + (setf player-threads (cons thread player-threads)) + (sleep 1))))) ;;give the socket a chance to connect + + (defun handle-connection (socket) + "Answer requests until the player disconnects" + (usocket:with-connected-socket (conn (usocket:socket-accept socket)) + (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' + (do* ((sockstr (usocket:socket-stream conn)) + (request (read-line sockstr NIL) (read-line sockstr NIL))) + ;;TODO remove player-thread from list when terminated + ((or (not running) (null request))) + (format sockstr "~S~%" (to-string (answer request))) + (finish-output sockstr))))) + +(defun answer (request) + (logging "SERVER: received request ~S" request) + (let* ((reqelts (extract-elements request)) + (player-name (first reqelts)) + (cmd (second reqelts)) + (args (cddr reqelts))) + (cond ((eq player-name 'ACK) "ACK ACK") ;debug + ((eq cmd 'get-map) (get-map player-name)) + ((eq cmd 'describe-patch) (describe-patch args)) + ;;TODO + ))) + +;;; COMMUNICATION FUNCTIONS + +(defun get-map (player-name) + "Return a 2d list of map places (each a list of a char and a colour)" + ;;TODO + ) + +(defun describe-patch (coords) + "Return a list of lines describing the patch at these coordinates." + (let ((p (coord (first coords) (second coords)))) + (list (string-upcase (biome-name (patch-biome p))) "" + (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" + (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) + (when (patch-occupant p) ;TODO players -> "$name is here." + (format NIL "There is ~A here." + (leading-vowel (.name (patch-occupant p))))) + (when (patch-items p) + (format NIL "The following items are here:~A *~A" #\newline + (string-from-list (mapcar #'.name (patch-items p))) + (format NIL "~% *")))))) diff --git a/src/util.lisp b/src/util.lisp new file mode 100644 index 0000000..341e15c --- /dev/null +++ b/src/util.lisp @@ -0,0 +1,339 @@ +;;; +;;; This is a personal utility module that I use in most of my Common Lisp +;;; projects. It originated in the Atlantis project, but has been used many +;;; times since and evolved accordingly. +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(in-package :naledi-ya-africa) ;;XXX change to dv-utils? + +;;; MACROS + +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + +(defmacro logging (str &rest format-args) + "Write an entry to *logfile*" + ;;FIXME gives error if cwd != file directory + `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) + +(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))) + ((null vl) expr) + (setf (cdr (last expr)) (list var)) + (setf (cdr (last expr)) (list value)))) + +(defmacro input (&rest vars) + "Take input from terminal and store each element in a passed variable" + ;; XXX Add a prompt parameter again? + `(progn + (format t "~&>>> ") + (set-list (read) ,@vars) + (first (list ,@vars)))) + +(defmacro input-string (&optional (var (gensym))) + "Read a string input line" + `(progn + (format t "~&>>> ") + (setf ,var (read-line)) + ,var)) + +(defmacro while (condition &body body) + "An implementation of a while loop as found in other languages" + `(do () + ((not ,condition) NIL) + ,@body)) + +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) + +(defmacro cassoc (entry table &key (test '#'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-nth (index lst) + "Return (nth index lst), or NIL if index is out of range" + `(if (> ,index (1- (length ,lst))) + NIL (nth ,index ,lst))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + +;;; FUNCTIONS + +;; Some of these functions are probably quite inefficient (lots of consing) + +(defun remove-first-if (fn lst) + ;;FIXME isn't this identical to `remove-if'? + "Remove the first element in a list that satisfies the given predicate" + (cond ((null lst) NIL) + ((funcall fn (car lst)) (cdr lst)) + (T (cons (car lst) (remove-first-if fn (cdr lst)))))) + +(defun average (&rest numbers) + "Compute the average of the given numbers" + (/ (reduce #'+ numbers) (length numbers))) + +(defun halve (n &optional (round-fn 'round)) + "Halve a given number and round it to an integer." + (let ((half (/ n 2))) + (cond ((eq round-fn 'up) (ceiling half)) + ((eq round-fn 'down) (floor half)) + (T (round half))))) + +(defun keys (assoc-list) + "Return a list of the keys in an association list" + (if (null assoc-list) NIL + (cons (car (car assoc-list)) (keys (cdr assoc-list))))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components :sep ""))) + +(defun symbol-to-string (sym) + "Convert a symbol to a string, exchanging dashes for spaces" + (string-from-list + (split-string (string-downcase (string sym)) #\-) #\space)) + +(defun letter-in-string (letter str) + "Is this letter in this string?" + (dotimes (i (length str) NIL) + (when (equalp (aref str i) letter) (return T)))) + +;; TODO change &optional to &key (and figure out why the heck that doesn't +;; work - clisp bug?), add null-filler keyword +(defun string-from-list (lst &optional (separator #\space)) + "Put all elements of lst into a single string, separated by the separator" + (cond ((null lst) "") + ((= (length lst) 1) (to-string (car lst))) + (T (concatenate 'string (to-string (first lst)) (to-string separator) + (string-from-list (cdr lst) separator))))) + +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (if (or (minusp i) (> i (length s))) s + (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) + (dotimes (c (length s) (list s1 s2)) + (if (> i c) + (setf (aref s1 c) (aref s c)) + (setf (aref s2 (- c i)) (aref s c))))))) + +(defun char-list-to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun trim-whitespace (s) + "Trim off spaces and tabs before and after string s" + (string-trim '(#\space #\tab) s)) + +(defun to-string (x) + "Whatever x is, convert it into a string" + (cond ((stringp x) x) + ((or (symbolp x) (characterp x)) (string x)) + (t (format NIL "~S" x)))) + +(defun leading-vowel (noun) + "Return noun prepended with 'a' or 'an', depending on its first letter." + (format NIL "~A ~A" + (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") + noun)) + +(defun break-lines (lines width &optional (indent "")) + "Take a list of lines and break any that are too long." + (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) + ((null ls) result) + (if (<= (length l) width) + (setf result (append result (list l))) + (setf ls (append (list nil (first (cut-string l width)) + (concatenate 'string (to-string indent) + (second (cut-string l width)))) + (cdr ls)))))) + +(defun extract-elements (str) + "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" + (multiple-value-bind (next-element i) (read-from-string str nil) + (if (null next-element) NIL + (cons next-element + (extract-elements (second (cut-string str i))))))) + +(defun count-instances (search-term search-sequence &key (test #'eql)) + "Count the number of instances of search-term in search-sequence" + (let ((count 0)) + (dotimes (i (length search-sequence) count) + (when (funcall test search-term (elt search-sequence i)) + (incf count))))) + +(defun most-common-element (lst &key (test #'eql)) + "Return the most common element in this list and how often it appears" + ;;This function has multiple return values! + ;;In case of multiple mces, return the one that appears first + (let ((elements-counted NIL) (max 0) (mce NIL)) + (dolist (e lst (values mce max)) + (unless (member e elements-counted :test test) + (let ((count (count-instances e lst :test test))) + (when (> count max) + (setf max count) + (setf mce e))) + (setf elements-counted (append elements-counted (list e))))))) + +(defun nths (n lst) + "Take in a list of lists and return the nth element of each" + (when (and lst (listp (car lst))) + (cons (nth n (car lst)) (nths n (cdr lst))))) + +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + +(defun range (stop &key (start 0) (step 1)) + "Return a list of numbers from start to stop" + ;;XXX Surely this must exist as a function in Common Lisp already, + ;; I just don't know what it's called... + (unless (>= start stop) + (cons start (range stop :start (+ start step) :step step)))) + +(defun to-list (vector &optional (next-elt 0)) + "Turn the vector into a list" + (if (= next-elt (1- (length vector))) NIL + (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) + +(defun cut-list (l i) + "Cut list l in two at index i and return the two sublists in a list" + (if (or (< i 1) (> i (length l))) l + (do* ((lst2 l (cdr lst2)) + (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) + ((= i (length lst1)) (list lst1 (cdr lst2)))))) + +(defun random-elt (seq) + "Return a random element of this sequence" + (elt seq (random (length seq)))) + +(defun random-offset (n max-offset) + "Return n plus a random offset" + (+ n (- (random (* 2 max-offset)) max-offset))) + +(defun chancep (p) + "Do a random test with 1/p probability of success." + (= 1 (random p))) + +(defun probabilityp (percent) + "Do a random test, with the percentage giving the success probability" + ;;Accuracy: 0.1 + (> percent (/ (random 1000) 10))) + +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + (with-open-file (f file-name) + (do* ((line (read-line f nil nil) + (read-line f nil nil)) + (file-lines (list line) (append file-lines (list line)))) + ((null line) file-lines)))) + +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + +(defun write-to-file (text filename &optional (append NIL)) + "Write text (a string or list of strings) to the specified file" + (let ((text-list (if (listp text) text (list text))) + (f (if append + (open filename :direction :output + :if-exists :append :if-does-not-exist :create) + (open filename :direction :output :if-exists :supersede)))) + (dolist (line text-list) + (format f "~&~A~&" line)) + (close f))) + +(defun write-list (lst filename) + "Write a list to file, one entry per line." + (let ((f (open filename :direction :output))) + (dolist (i lst) (format f "~&~S" i)) + (close f))) + +(defun time-stamp (&optional (time-t (get-universal-time))) + (let ((time (multiple-value-list (decode-universal-time time-t)))) + (format NIL "~S/~S/~S ~S:~S" + (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components ""))) + +(defun make-list-function (container-type &optional (add-s t)) + "Return a function to return a list of the names of all objects of the +specified type in the container struct" + #'(lambda (object-type container) + (let* ((get-objects (symbol-function + (build-symbol container-type "-" + object-type (if add-s "s" "")))) + (get-object-name (symbol-function + (build-symbol object-type "-name"))) + (objects (funcall get-objects container)) (name-list NIL)) + (dolist (o objects name-list) + (when (stringp o) (return objects)) + (setf name-list + (cons (funcall get-object-name o) name-list)))))) + +;; DEPRECATED - replace with number-menu +(defun choose-number-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)) + +;; DEPRECATED - replace with number-menu +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +;; SUPERSEDES choose-number-option AND choose-option! +(defun number-menu (entries &optional (header "Please choose an option:")) + "Display a number-based menu and act on the user's choice" + ;; The param entries is a list of lists in the form + ;; ( ) + (format t "~&~A" header) + (dotimes (i (length entries)) + (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) + (format t "~&>>> ") + (setf choice (read)) + (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) + (format t "~&Invalid choice! Please choose again:~%>>> ") + (setf choice (read))) + (funcall (second (nth choice entries)))) diff --git a/src/world.lisp b/src/world.lisp new file mode 100644 index 0000000..5365554 --- /dev/null +++ b/src/world.lisp @@ -0,0 +1,187 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines patches and administrates the world object. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defstruct patch + (pos '(0 0)) ;position + (biome NIL) + (items '()) + (occupant NIL)) + +(defstruct biome + (name "") + (ground "") + (features '()) ;an alist of possible features and their 1/p probabilities + (char #\.) ;default map display character + (col ':white)) ;default map display colour + +;; BIOME LIST + +(let ((biome-list NIL)) + (defun register-biome (symbol-name biome-object) + (setf biome-list (cons (list symbol-name biome-object) biome-list))) + + (defun available-biomes () + (keys biome-list)) + + (defun get-biome (symbol-name) + (cassoc symbol-name biome-list))) + +(defmacro new-biome (name &body body) + `(register-biome ',name + (make-biome + :name ,(symbol-to-string name) + ,@body))) + +;; MATRIX FUNCTIONS + +(defun init-matrix (size) + "Create a square matrix of empty patches" + ;;TODO change this to arrays for performance + (debugging "~&Creating a ~S/~S matrix." size size) + (do ((y 0 (1+ y)) (world NIL) (row NIL NIL)) + ((= y size) world) + (dotimes (x size) + (setf row (append row (list (make-patch :pos (list x y)))))) + (setf world (append world (list row))))) + +(defun distance (x1 y1 x2 y2 &optional (pythag NIL)) + "Find the distance between two sets of coordinates" + (if pythag (round (sqrt (+ (expt (- x1 x2) 2) (expt (- y1 y2) 2)))) + (min (abs (- x1 x2)) (abs (- y1 y2))))) + +(defun closest-coords (here coord-list &optional (abs-dist NIL)) + "Find the closest position to 'here' from a list of coordinates" + (do* ((clist coord-list (cdr clist)) (c (car clist) (car clist)) + (dist (when c (distance (first here) (second here) + (first c) (second c) abs-dist)) + (when c (distance (first here) (second here) + (first c) (second c) abs-dist))) + (mindist dist) (closest c)) + ((null clist) closest) + (when (< dist mindist) + (setf mindist dist closest c)))) + +(defun opposite-dir (dir) + "Return the direction opposite the input" + (let ((pos (position dir *directions*))) + (when pos (nth (rem (+ 4 pos) 8) *directions*)))) + +(defun next-dir (dir &optional (cw T)) + "Get the neighbouring direction (clockwise or anticlockwise)" + (let ((pos (position dir *directions*)) + (diff (if cw 1 -1))) + (when pos (nth (rem (+ diff pos 8) 8) *directions*)))) + +(defun orth-dir (dir &optional (cw T)) + "Get the direction orthogonal (at right angles) to the given one." + (next-dir (next-dir dir cw) cw)) + +(defun diagonalp (dir) + "Is dir a diagonal direction?" + (member dir '(NE SE SW NW) :test #'eq)) + +(defun dir2patch (herex herey therex therey) + "Calculate the direction to a patch" + (cond ((> herex therex) + (cond ((> herey therey) 'NW) + ((< herey therey) 'SW) + (T 'W))) + ((< herex therex) + (cond ((> herey therey) 'NE) + ((< herey therey) 'SE) + (T 'E))) + (T (cond ((> herey therey) 'N) + ((< herey therey) 'S) + (T NIL))))) + +(defun coordsindir (x y dir) + "Return the coordinates in the given direction" + (cond ((eq dir 'N) (list x (1- y))) + ((eq dir 'NE) (list (1+ x) (1- y))) + ((eq dir 'E) (list (1+ x) y)) + ((eq dir 'SE) (list (1+ x) (1+ y))) + ((eq dir 'S) (list x (1+ y))) + ((eq dir 'SW) (list (1- x) (1+ y))) + ((eq dir 'W) (list (1- x) y)) + ((eq dir 'NW) (list (1- x) (1- y))) + ((null dir) (list x y)) + (T (error "~&Invalid direction ~S")))) + +(defun patchindir (x y dir) + "Return the patch in the given direction" + (let* ((coords (coordsindir x y dir)) + (nextx (first coords)) (nexty (second coords))) + (coord nextx nexty))) + +(defun neighbour (p dir) + "Return the neighbouring patch in this direction" + (patchindir (first (patch-pos p)) (second (patch-pos p)) dir)) + + +;; WORLD CREATION FUNCTIONS + +(defun get-patch-feature (patch) + "Find a random feature (or none) to occupy this patch." + (let ((flist (biome-features (patch-biome patch)))) + (dolist (f flist NIL) + (when (chancep (second f)) + (return-from get-patch-feature + (make-instance (first f) :x (first (patch-pos patch)) + :y (second (patch-pos patch)))))))) + +(defun generate-biomes (size-factor) + ;;XXX The maps this produces don't look quite as expected, but for + ;; current purposes they are good enough + (debugging "~&Generating biomes") ;debug + (let* ((wsize (world-size)) (seeds NIL) + (nseeds (round (/ wsize size-factor))) + (biomes (remove-first-if + #'(lambda (e) (eq e 'stream)) + (available-biomes)))) + ;;Initialize a set of biome 'seed' coordinates + (dotimes (n nseeds) + (setf seeds + (cons (list (random wsize) + (random wsize) + (random-elt biomes)) + seeds))) + (debugging "~&~S" seeds) + ;;For each patch, calculate the closest seed and set to that biome + (dotimes (x wsize seeds) + (dotimes (y wsize) + (let ((p (coord x y)) + (b (third (closest-coords (list x y) seeds T)))) + (setf (patch-biome p) (get-biome b)) + (setf (patch-occupant p) (get-patch-feature p))))))) + +(defun generate-stream (x0 y0) + (debugging "~&Generating a stream, starting at ~S/~S" x0 y0) ;debug + (do* ((dir (random-elt *directions*) + (if (probabilityp 75) dir (next-dir dir (random-elt '(T NIL))))) + (patch (coord x0 y0) (neighbour patch dir))) + ((or (null patch) (eq (patch-biome patch) (get-biome 'stream)))) + (setf (patch-biome patch) (get-biome 'stream)) + (setf (patch-occupant patch) NIL))) + +(defun create-world (size) + "Create a world of the specified size (square)" + (set-world (init-matrix size)) + ;;XXX magic numbers + (generate-biomes 10) + (dotimes (s (round (/ (expt size 2) 2000))) + (generate-stream (random size) (random size)))) + +(defun init-world () + "Initialize the log, RNG, and world." + (write-to-file "NALEDI ya AFRICA" *logfile*) + (logging (time-stamp)) + (setf *random-state* (make-random-state t)) + (create-world *world-size*)) diff --git a/util.lisp b/util.lisp deleted file mode 100644 index 3d992b7..0000000 --- a/util.lisp +++ /dev/null @@ -1,336 +0,0 @@ -;;; -;;; This is a personal utility module that I use in most of my Common Lisp -;;; projects. It originated in the Atlantis project, but has been used many -;;; times since and evolved accordingly. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 09/05/2015 -;;; - -;;; MACROS - -(defmacro debugging (str &rest format-args) - "If *debugging* is true, print str" - `(when *debugging* (format t ,str ,@format-args))) - -(defmacro logging (str &rest format-args) - "Write an entry to *logfile*" - `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) - -(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))) - ((null vl) expr) - (setf (cdr (last expr)) (list var)) - (setf (cdr (last expr)) (list value)))) - -(defmacro input (&rest vars) - "Take input from terminal and store each element in a passed variable" - ;; XXX Add a prompt parameter again? - `(progn - (format t "~&>>> ") - (set-list (read) ,@vars) - (first (list ,@vars)))) - -(defmacro input-string (&optional (var (gensym))) - "Read a string input line" - `(progn - (format t "~&>>> ") - (setf ,var (read-line)) - ,var)) - -(defmacro while (condition &body body) - "An implementation of a while loop as found in other languages" - `(do () - ((not ,condition) NIL) - ,@body)) - -(defmacro != (object1 object2 &key (test 'eql)) - "A not-equals macro to save some typing" - `(not (,test ,object1 ,object2))) - -(defmacro cassoc (entry table &key (test #'eql)) - "Returns (car (cdr (assoc entry table)))" - `(car (cdr (assoc ,entry ,table :test ,test)))) - -(defmacro safe-nth (index lst) - "Return (nth index lst), or NIL if index is out of range" - `(if (> ,index (1- (length ,lst))) - NIL (nth ,index ,lst))) - -(defmacro safe-aref (vector index) - "Return (aref vector index), but return NIL if out of range" - `(if (> ,index (1- (length ,vector))) - NIL (aref ,vector ,index))) - -(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) - "A macro analogous to dolist" - (let-gensyms (index) - `(do* ((,index 0 (1+ ,index)) - (,element (safe-aref ,vector ,index) - (safe-aref ,vector ,index))) - ((= ,index (length ,vector)) ,return-variable) - ,@body))) - -;;; FUNCTIONS - -;; Some of these functions are probably quite inefficient (lots of consing) - -(defun remove-first-if (fn lst) - ;;FIXME isn't this identical to `remove-if'? - "Remove the first element in a list that satisfies the given predicate" - (cond ((null lst) NIL) - ((funcall fn (car lst)) (cdr lst)) - (T (cons (car lst) (remove-first-if fn (cdr lst)))))) - -(defun average (&rest numbers) - "Compute the average of the given numbers" - (/ (reduce #'+ numbers) (length numbers))) - -(defun halve (n &optional (round-fn 'round)) - "Halve a given number and round it to an integer." - (let ((half (/ n 2))) - (cond ((eq round-fn 'up) (ceiling half)) - ((eq round-fn 'down) (floor half)) - (T (round half))))) - -(defun keys (assoc-list) - "Return a list of the keys in an association list" - (if (null assoc-list) NIL - (cons (car (car assoc-list)) (keys (cdr assoc-list))))) - -(defun build-symbol (&rest components) - "Concatenate the passed components into a single symbol" - (read-from-string (string-from-list components :sep ""))) - -(defun symbol-to-string (sym) - "Convert a symbol to a string, exchanging dashes for spaces" - (string-from-list - (split-string (string-downcase (string sym)) #\-) #\space)) - -(defun letter-in-string (letter str) - "Is this letter in this string?" - (dotimes (i (length str) NIL) - (when (equalp (aref str i) letter) (return T)))) - -;; TODO change &optional to &key (and figure out why the heck that doesn't -;; work - clisp bug?), add null-filler keyword -(defun string-from-list (lst &optional (separator #\space)) - "Put all elements of lst into a single string, separated by the separator" - (cond ((null lst) "") - ((= (length lst) 1) (to-string (car lst))) - (T (concatenate 'string (to-string (first lst)) (to-string separator) - (string-from-list (cdr lst) separator))))) - -(defun split-string (str separator) - "Split the string up into a list of strings along the separator character" - (cond ((equalp str (to-string separator)) NIL) - ((zerop (count-instances separator str)) (list str)) - (T (let ((split-elt (cut-string str (position separator str)))) - (cons (first split-elt) - (split-string (second (cut-string (second split-elt) 1)) - separator)))))) - -(defun cut-string (s i) - "Cut string s in two at index i and return the two substrings in a list" - (if (or (minusp i) (> i (length s))) s - (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) - (dotimes (c (length s) (list s1 s2)) - (if (> i c) - (setf (aref s1 c) (aref s c)) - (setf (aref s2 (- c i)) (aref s c))))))) - -(defun char-list-to-string (char-list) - "Convert a character list to a string" - (let ((s (make-string (length char-list) :initial-element #\SPACE))) - (dotimes (i (length char-list) s) - (setf (aref s i) (nth i char-list))))) - -(defun trim-whitespace (s) - "Trim off spaces and tabs before and after string s" - (string-trim '(#\space #\tab) s)) - -(defun to-string (x) - "Whatever x is, convert it into a string" - (cond ((stringp x) x) - ((or (symbolp x) (characterp x)) (string x)) - (t (format NIL "~S" x)))) - -(defun leading-vowel (noun) - "Return noun prepended with 'a' or 'an', depending on its first letter." - (format NIL "~A ~A" - (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") - noun)) - -(defun break-lines (lines width &optional (indent "")) - "Take a list of lines and break any that are too long." - (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) - ((null ls) result) - (if (<= (length l) width) - (setf result (append result (list l))) - (setf ls (append (list nil (first (cut-string l width)) - (concatenate 'string (to-string indent) - (second (cut-string l width)))) - (cdr ls)))))) - -(defun extract-elements (str) - "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" - (multiple-value-bind (next-element i) (read-from-string str nil) - (if (null next-element) NIL - (cons next-element - (extract-elements (second (cut-string str i))))))) - -(defun count-instances (search-term search-sequence &key (test #'eql)) - "Count the number of instances of search-term in search-sequence" - (let ((count 0)) - (dotimes (i (length search-sequence) count) - (when (funcall test search-term (elt search-sequence i)) - (incf count))))) - -(defun most-common-element (lst &key (test #'eql)) - "Return the most common element in this list and how often it appears" - ;;This function has multiple return values! - ;;In case of multiple mces, return the one that appears first - (let ((elements-counted NIL) (max 0) (mce NIL)) - (dolist (e lst (values mce max)) - (unless (member e elements-counted :test test) - (let ((count (count-instances e lst :test test))) - (when (> count max) - (setf max count) - (setf mce e))) - (setf elements-counted (append elements-counted (list e))))))) - -(defun nths (n lst) - "Take in a list of lists and return the nth element of each" - (when (and lst (listp (car lst))) - (cons (nth n (car lst)) (nths n (cdr lst))))) - -(defun set-p (lst) - "Is lst a set (i.e. no elements occur more than once)?" - (cond ((null lst) T) - ((member (car lst) (cdr lst)) NIL) - (T (set-p (cdr lst))))) - -(defun range (stop &key (start 0) (step 1)) - "Return a list of numbers from start to stop" - ;;XXX Surely this must exist as a function in Common Lisp already, - ;; I just don't know what it's called... - (unless (>= start stop) - (cons start (range stop :start (+ start step) :step step)))) - -(defun to-list (vector &optional (next-elt 0)) - "Turn the vector into a list" - (if (= next-elt (1- (length vector))) NIL - (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) - -(defun cut-list (l i) - "Cut list l in two at index i and return the two sublists in a list" - (if (or (< i 1) (> i (length l))) l - (do* ((lst2 l (cdr lst2)) - (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) - ((= i (length lst1)) (list lst1 (cdr lst2)))))) - -(defun random-elt (seq) - "Return a random element of this sequence" - (elt seq (random (length seq)))) - -(defun random-offset (n max-offset) - "Return n plus a random offset" - (+ n (- (random (* 2 max-offset)) max-offset))) - -(defun chancep (p) - "Do a random test with 1/p probability of success." - (= 1 (random p))) - -(defun probabilityp (percent) - "Do a random test, with the percentage giving the success probability" - ;;Accuracy: 0.1 - (> percent (/ (random 1000) 10))) - -(defun load-text-file (file-name) - "Load a text file into a list of strings (representing the lines)" - (with-open-file (f file-name) - (do* ((line (read-line f nil nil) - (read-line f nil nil)) - (file-lines (list line) (append file-lines (list line)))) - ((null line) file-lines)))) - -(defun print-text-file (file-name) - "Print out the contents of this text file" - (dolist (line (load-text-file file-name)) - (unless (null line) (format t "~%~A" line)))) - -(defun write-to-file (text filename &optional (append NIL)) - "Write text (a string or list of strings) to the specified file" - (let ((text-list (if (listp text) text (list text))) - (f (if append - (open filename :direction :output - :if-exists :append :if-does-not-exist :create) - (open filename :direction :output :if-exists :supersede)))) - (dolist (line text-list) - (format f "~&~A~&" line)) - (close f))) - -(defun write-list (lst filename) - "Write a list to file, one entry per line." - (let ((f (open filename :direction :output))) - (dolist (i lst) (format f "~&~S" i)) - (close f))) - -(defun time-stamp (&optional (time-t (get-universal-time))) - (let ((time (multiple-value-list (decode-universal-time time-t)))) - (format NIL "~S/~S/~S ~S:~S" - (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) - -(defun build-symbol (&rest components) - "Concatenate the passed components into a single symbol" - (read-from-string (string-from-list components ""))) - -(defun make-list-function (container-type &optional (add-s t)) - "Return a function to return a list of the names of all objects of the -specified type in the container struct" - #'(lambda (object-type container) - (let* ((get-objects (symbol-function - (build-symbol container-type "-" - object-type (if add-s "s" "")))) - (get-object-name (symbol-function - (build-symbol object-type "-name"))) - (objects (funcall get-objects container)) (name-list NIL)) - (dolist (o objects name-list) - (when (stringp o) (return objects)) - (setf name-list - (cons (funcall get-object-name o) name-list)))))) - -;; DEPRECATED - replace with number-menu -(defun choose-number-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)) - -;; DEPRECATED - replace with number-menu -(defun choose-option (option-list) - "Like choose-number-option, but return the value of the choice" - ;; Basically just a utility wrapper - (nth (choose-number-option option-list) option-list)) - -;; SUPERSEDES choose-number-option AND choose-option! -(defun number-menu (entries &optional (header "Please choose an option:")) - "Display a number-based menu and act on the user's choice" - ;; The param entries is a list of lists in the form - ;; ( ) - (format t "~&~A" header) - (dotimes (i (length entries)) - (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) - (format t "~&>>> ") - (setf choice (read)) - (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) - (format t "~&Invalid choice! Please choose again:~%>>> ") - (setf choice (read))) - (funcall (second (nth choice entries)))) diff --git a/animals.lisp b/animals.lisp deleted file mode 100644 index a253200..0000000 --- a/animals.lisp +++ /dev/null @@ -1,69 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines animal and species structs and the various species -;;;; found in-game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX bird species? -;;XXX water species? -;;XXX small mammals -> hills - -(new-item animal impala - :strength 3 :max-health 10 - :aggression 0 :group-size 15 - :habitat '(forest grassland) - :char #\a :color :yellow - :drops NIL) ;TODO - -(new-item animal warthog - :strength 5 :max-health 10 - :aggression 2 :group-size 5 - :habitat '(forest grassland swamp hill) - :char #\p :color :magenta - :drops NIL) ;TODO - -(new-item animal lion - :strength 18 :max-health 25 - :aggression 20 :group-size 6 - :habitat '(grassland swamp desert) - :char #\C :color :red - :drops NIL) ;TODO - -(new-item animal leopard - :strength 20 :max-health 30 - :aggression 20 :group-size 1 - :habitat '(forest grassland hill) - :char #\C :color :magenta - :drops NIL) ;TODO - -(new-item animal cheetah - :strength 15 :max-health 20 - :aggression 10 :group-size 1 - :habitat '(grassland) - :char #\C :color :yellow - :drops NIL) ;TODO - -(new-item animal elephant - :strength 40 :max-health 50 - :aggression 5 :group-size 4 - :habitat '(forest grassland) - :char #\E :color :cyan - :drops NIL) ;TODO - -(new-item animal buffalo - :strength 15 :max-health 20 - :aggression 5 :group-size 10 - :habitat '(swamp grassland) - :char #\B :color :magenta - :drops NIL) ;TODO - -(new-item animal oryx - :strength 8 :max-health 12 - :aggression 0 :group-size 2 - :habitat '(desert grassland) - :char #\a :color :blue - :drops NIL) ;TODO diff --git a/biomes.lisp b/biomes.lisp deleted file mode 100644 index 74181d8..0000000 --- a/biomes.lisp +++ /dev/null @@ -1,50 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; Biomes are habitat types, that provide default functions for patches. -;;;; This file holds the biome definitions. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX lake? -;;TODO optimise frequency numbers - -(new-biome grassland - :ground "tall elephant grass" - :char #\; :col ':yellow - :features '((acacia 100) (boulder 200) - (impala 500) (warthog 600) - (lion 2400) (leopard 3000) - (cheetah 2400) (elephant 800) - (buffalo 1000))) ;TODO - -(new-biome forest - :ground "leaf litter and small shrubs" - :char #\. :col ':green - :features '((miombo 10) (acacia 15) - (warthog 500) (impala 800) - (elephant 1000) (leopard 3200))) ;TODO - -(new-biome stream - :ground "shallow flowing water" - :char #\~ :col ':blue - :features '()) ;TODO - -(new-biome swamp - :ground "short sedge grass growing on boggy black soil" - :char #\w :col ':green - :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO - -(new-biome hill - :ground "hard, stony soil" - :char #\m :col ':cyan ;XXX very bright - hurts the eyes... - :features '((boulder 20) (miombo 80) - (warthog 600) (leopard 3200))) ;TODO - -(new-biome desert - :ground "deep, drifting sand" - :char #\^ :col ':white ;XXX yellow would be better, but shows up brown - :features '((cactus 100) (boulder 200) - (oryx 800) (lion 3600))) ;TODO diff --git a/client.lisp b/client.lisp deleted file mode 100644 index 3031931..0000000 --- a/client.lisp +++ /dev/null @@ -1,40 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for connecting to the server and provides the -;;;; API functions for doing so. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(let ((naledi-server NIL)) - (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) - "Connect to the specified server" - ;;FIXME I need to catch some exceptions here... - (setf naledi-server (socket-connect ip port)) - (if naledi-server - (notify "Connected to server ~A:~A" ip port) - (notify "Connection to server ~A:~A failed." ip port))) - - (defun current-server () naledi-server) ;TODO remove after development - - (defun query-server (request) - "Send a request string to the server and return the answer" - (unless naledi-server ;XXX do this with exceptions - (return-from query-server "You are not connected to a server!")) - (let ((servstr (socket-stream naledi-server))) - (logging "CLIENT: sending request ~S" request) - (format servstr "~A~%" request) - (finish-output servstr) - ;;FIXME server still doesn't receive string until disconnect... - (logging "CLIENT: waiting for server response") - (wait-for-input naledi-server) - (read-from-string (read-line servstr)))) - - (defun disconnect () - "Disconnect from the server" - (when naledi-server - (socket-close naledi-server) - (setf naledi-server NIL) - (notify "Disconnected from server.")))) diff --git a/content/animals.lisp b/content/animals.lisp new file mode 100644 index 0000000..3454951 --- /dev/null +++ b/content/animals.lisp @@ -0,0 +1,71 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines animal and species structs and the various species +;;;; found in-game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX bird species? +;;XXX water species? +;;XXX small mammals -> hills + +(new-item animal impala + :strength 3 :max-health 10 + :aggression 0 :group-size 15 + :habitat '(forest grassland) + :char #\a :color :yellow + :drops NIL) ;TODO + +(new-item animal warthog + :strength 5 :max-health 10 + :aggression 2 :group-size 5 + :habitat '(forest grassland swamp hill) + :char #\p :color :magenta + :drops NIL) ;TODO + +(new-item animal lion + :strength 18 :max-health 25 + :aggression 20 :group-size 6 + :habitat '(grassland swamp desert) + :char #\C :color :red + :drops NIL) ;TODO + +(new-item animal leopard + :strength 20 :max-health 30 + :aggression 20 :group-size 1 + :habitat '(forest grassland hill) + :char #\C :color :magenta + :drops NIL) ;TODO + +(new-item animal cheetah + :strength 15 :max-health 20 + :aggression 10 :group-size 1 + :habitat '(grassland) + :char #\C :color :yellow + :drops NIL) ;TODO + +(new-item animal elephant + :strength 40 :max-health 50 + :aggression 5 :group-size 4 + :habitat '(forest grassland) + :char #\E :color :cyan + :drops NIL) ;TODO + +(new-item animal buffalo + :strength 15 :max-health 20 + :aggression 5 :group-size 10 + :habitat '(swamp grassland) + :char #\B :color :magenta + :drops NIL) ;TODO + +(new-item animal oryx + :strength 8 :max-health 12 + :aggression 0 :group-size 2 + :habitat '(desert grassland) + :char #\a :color :blue + :drops NIL) ;TODO diff --git a/content/biomes.lisp b/content/biomes.lisp new file mode 100644 index 0000000..4f20bd9 --- /dev/null +++ b/content/biomes.lisp @@ -0,0 +1,52 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; Biomes are habitat types, that provide default functions for patches. +;;;; This file holds the biome definitions. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX lake? +;;TODO optimise frequency numbers + +(new-biome grassland + :ground "tall elephant grass" + :char #\; :col ':yellow + :features '((acacia 100) (boulder 200) + (impala 500) (warthog 600) + (lion 2400) (leopard 3000) + (cheetah 2400) (elephant 800) + (buffalo 1000))) ;TODO + +(new-biome forest + :ground "leaf litter and small shrubs" + :char #\. :col ':green + :features '((miombo 10) (acacia 15) + (warthog 500) (impala 800) + (elephant 1000) (leopard 3200))) ;TODO + +(new-biome stream + :ground "shallow flowing water" + :char #\~ :col ':blue + :features '()) ;TODO + +(new-biome swamp + :ground "short sedge grass growing on boggy black soil" + :char #\w :col ':green + :features '((warthog 500) (buffalo 400) (lion 3000))) ;TODO + +(new-biome hill + :ground "hard, stony soil" + :char #\m :col ':cyan ;XXX very bright - hurts the eyes... + :features '((boulder 20) (miombo 80) + (warthog 600) (leopard 3200))) ;TODO + +(new-biome desert + :ground "deep, drifting sand" + :char #\^ :col ':white ;XXX yellow would be better, but shows up brown + :features '((cactus 100) (boulder 200) + (oryx 800) (lion 3600))) ;TODO diff --git a/content/items.lisp b/content/items.lisp new file mode 100644 index 0000000..c0d4e5f --- /dev/null +++ b/content/items.lisp @@ -0,0 +1,87 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines in-game items. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;TODO split up into several files for each category + +;;; RESOURCE ITEMS + +;;TODO sand, glass, clay, bricks + +(new-item resource wood + :description "A block of wood, just right for working with" + :weight 1 :burning-product 'charcoal) + +(new-item resource charcoal + :description "A black lump of charcoal. Burns better than wood." + :weight 1) + +(new-item resource stone + :description "A fist-sized stone. What are you going to do with it?" + :weight 2) + +(new-item resource iron-ore + :description "A lump of iron ore - still needs to be smelted." + :weight 4 :burning-product 'iron) + +(new-item resource iron + :description "An iron ingot, ready for further crafting." + :weight 3) + +;;; LANDSCAPE FEATURES + +(new-item feature acacia + :description "A tall acacia tree, spreading its branches wide." + :destroy-with '(wood) + :drops '(wood) :weight 2000 + :char #\T :color :green) + +(new-item feature miombo + :description "A small, crooked miombo tree." + :destroy-with '(wood) + :drops '(wood) :weight 500 + :char #\Y :color :green) + +(new-item feature boulder + :description "A huge lump of grey basalt, sticking out of the ground." + :destroy-with '(stone) + :drops '(stone) :weight 5000 + :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( + +(new-item feature cactus + :description "A prickly eupharbacae sticks out of the ground here - don't touch!" + :destroy-with '(axe) ;does it drop anything? + :char #\+ :color :green) + +;;TODO termite hill, palm, baobab, pond + +;;; TOOL ITEMS + +;;TODO bronze tools, weapons + +(new-item tool stone-axe + :description "An axe, crudely but effectively fashioned out of stone." + :type 'wood :level 1 + :weight 2 :craft-with '(stone wood)) + +(new-item tool iron-axe + :description "A finely honed iron axe, just right for chopping wood." + :type 'wood :level 3 + :weight 3 :craft-with '(iron wood)) + +(new-item tool stone-pickaxe + :description "A pickaxe, somewhat helplessly made from stone." + :type 'stone :level 1 + :weight 3 :craft-with '(stone stone wood)) + +(new-item tool iron-pickaxe + :description "A solid iron pickaxe. Prepare to split rock!" + :type 'stone :level 3 + :weight 4 :craft-with '(iron iron wood)) diff --git a/item-classes.lisp b/item-classes.lisp deleted file mode 100644 index c781194..0000000 --- a/item-classes.lisp +++ /dev/null @@ -1,73 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS classes used in the game. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defclass item () - ;; The base class of all game items. - ((name :accessor .name :initarg :name :initform "") - (description :accessor .description :initarg :description :initform "") - (weight :accessor .weight :initarg :weight :initform 0) - (movable :reader movablep :initarg :movable :initform T))) - -(defclass destructable (item) - ;; An item that can be destroyed - ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) - (health :accessor .health :initarg :health :initform 1) - (drops :reader .drops :initarg :drops :initform '()))) - -(defclass craftable (item) - ;; An item that can be crafted from other items or resources - ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) - (craft-with :reader craft-with :initarg :craft-with :initform '()))) - -(defclass feature (destructable) - ;; A landscape feature such as a rock, a tree, or an animal. - ((symbol-char :reader .char :initarg :char :initform NIL) - (symbol-color :reader .color :initarg :color :initform NIL) - (x-pos :accessor .x :initarg :x :initform 0) - (y-pos :accessor .y :initarg :y :initform 0)) - (:default-initargs :movable NIL)) - -(defclass resource (item) - ;; A resource that can be gathered and used to craft items. - ((burning-product :reader .burning-product :initarg :burning-product))) - -(defclass tool (craftable) - ;; A tool or weapon that can be crafted and used in-game. - ((level :reader .level :initarg :level :initform 0) - ;;TODO the type needs some thought (add/change to efficiency?) - (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood - (condition :accessor .condition :initarg :condition :initform 100))) - -(defclass container (craftable feature) - ;; An item that can store other items (and perhaps manipulate them) - ((contains :accessor .contains :initform '()) - (max-weight :reader .max-weight :initarg :max-weight :initform -1) - (capacity :reader .capacity :initarg :capacity :initform 0))) - -(defclass animal (feature) - ;; An animal species - (;; species properties - (strength :accessor .strength :initarg :strength :initform 1) - (max-health :accessor .max-health :initarg :max-health :initform 1) - (aggression :accessor .aggression :initarg :aggression :initform 0) - (group-size :reader .group-size :initarg :group-size :initform 1) - (habitat :reader .habitat :initarg :habitat :initform '()) - ;; individual properties - (id :reader .id :initarg :id :initform -1) - (last-move :accessor .last-move :initform 0)) - (:default-initargs :destroy-with '(weapon) :movable T)) - -;;TODO (defclass player (feature) - -;; Create a new class for each item type -(defmacro new-item (superclass name &body body) - `(defclass ,name (,superclass) () - (:default-initargs ,@body - :name (string-downcase (to-string ',name))))) - diff --git a/item-methods.lisp b/item-methods.lisp deleted file mode 100644 index 51e060c..0000000 --- a/item-methods.lisp +++ /dev/null @@ -1,53 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines all CLOS methods used in the game for the different -;;;; item classes. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;; Default `update' and `action' methods are NOP -(defmethod update ((i item))) -(defmethod action ((i item))) - -(defmethod attack ((d destructable) (tl tool)) - "Attack a destructable item with a tool or weapon" - ;;Returns either the damaged item or the items it drops when destroyed - (if (member (class-of tl) (.destructors d)) - (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) - (.drops d) d) - (progn (notify "You cannot attack ~A with ~A." - (leading-vowl (.name d)) (leading-vowel (.name tl))) - NIL))) - -;;TODO (defmethod attack ((f feature) (tl tool))) -;;TODO (defmethod attack ((a animal) (w weapon))) - -(defmethod update ((a animal)) - (when (> (age-of-the-world) (.last-move a)) - (random-move a) - (incf (.last-move a)))) - -(defmethod random-move ((a animal)) - "Move in a random direction within the species' habitat niche" - (do* ((dir (random-elt *directions*) (random-elt *directions*)) - (next-patch (patchindir (.x a) (.y a) dir) - (patchindir (.x a) (.y a) dir)) - (ttl 10 (1- ttl))) - ((zerop ttl) - ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) - NIL) - (when (and next-patch (null (patch-occupant next-patch)) - (member (read-from-string - (biome-name (patch-biome next-patch))) - (.habitat a))) - ;; (logging "The ~A at ~S/~S is moving ~S." - ;; (.name a) (.x a) (.y a) dir) - (setf (patch-occupant (coord (.x a) (.y a))) NIL) - (setf (.x a) (first (patch-pos next-patch)) - (.y a) (second (patch-pos next-patch))) - (setf (patch-occupant next-patch) a) - (return-from random-move a)))) - diff --git a/items.lisp b/items.lisp deleted file mode 100644 index 7037161..0000000 --- a/items.lisp +++ /dev/null @@ -1,84 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines in-game items. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - - -;;; RESOURCE ITEMS - -;;TODO sand, glass, clay, bricks - -(new-item resource wood - :description "A block of wood, just right for working with" - :weight 1 :burning-product 'charcoal) - -(new-item resource charcoal - :description "A black lump of charcoal. Burns better than wood." - :weight 1) - -(new-item resource stone - :description "A fist-sized stone. What are you going to do with it?" - :weight 2) - -(new-item resource iron-ore - :description "A lump of iron ore - still needs to be smelted." - :weight 4 :burning-product 'iron) - -(new-item resource iron - :description "An iron ingot, ready for further crafting." - :weight 3) - -;;; LANDSCAPE FEATURES - -(new-item feature acacia - :description "A tall acacia tree, spreading its branches wide." - :destroy-with '(wood) - :drops '(wood) :weight 2000 - :char #\T :color :green) - -(new-item feature miombo - :description "A small, crooked miombo tree." - :destroy-with '(wood) - :drops '(wood) :weight 500 - :char #\Y :color :green) - -(new-item feature boulder - :description "A huge lump of grey basalt, sticking out of the ground." - :destroy-with '(stone) - :drops '(stone) :weight 5000 - :char #\8 :color :white) ;XXX colour :gray is not supported on Arch :-( - -(new-item feature cactus - :description "A prickly eupharbacae sticks out of the ground here - don't touch!" - :destroy-with '(axe) ;does it drop anything? - :char #\+ :color :green) - -;;TODO termite hill, palm, baobab, pond - -;;; TOOL ITEMS - -;;TODO bronze tools, weapons - -(new-item tool stone-axe - :description "An axe, crudely but effectively fashioned out of stone." - :type 'wood :level 1 - :weight 2 :craft-with '(stone wood)) - -(new-item tool iron-axe - :description "A finely honed iron axe, just right for chopping wood." - :type 'wood :level 3 - :weight 3 :craft-with '(iron wood)) - -(new-item tool stone-pickaxe - :description "A pickaxe, somewhat helplessly made from stone." - :type 'stone :level 1 - :weight 3 :craft-with '(stone stone wood)) - -(new-item tool iron-pickaxe - :description "A solid iron pickaxe. Prepare to split rock!" - :type 'stone :level 3 - :weight 4 :craft-with '(iron iron wood)) diff --git a/naledi.asd b/naledi.asd new file mode 100644 index 0000000..73bb35e --- /dev/null +++ b/naledi.asd @@ -0,0 +1,40 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the Naledi system, to be loaded with asdf/quicklisp. +;;;; +;;;; Copy this directory to $QUICKLISP_DIR/local_projects, then run +;;;; (ql:quickload "naledi") to load. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(asdf:defsystem :naledi + :long-name "Naledi ya Africa" + :description "A multiplayer survival game set in Africa" + :author "Daniel Vedder " + :homepage "https://git.synoikos.de/daniel/naledi" + :license "MIT" + :version "0.1" + :depends-on (:bordeaux-threads :croatoan :usocket) + :serial t + :components + ((:file "package") + (:module "src" + :serial t + :components + ((:file "params") + (:file "util") + (:file "item-classes") + (:file "item-methods") + (:file "world") + (:file "server") + (:file "client"))) + (:module "content" + :components + ((:file "animals") + (:file "biomes") + (:file "items"))) + (:file "naledi")) + :entry-point "naledi:start-game") diff --git a/naledi.lisp b/naledi.lisp index df5bd7c..f631847 100644 --- a/naledi.lisp +++ b/naledi.lisp @@ -7,78 +7,65 @@ ;;;; (c) 2018 Daniel Vedder, MIT license ;;;; - -(defparameter *debugging* T) -(defparameter *logfile* "naledi.log") -(defparameter *world-size* 250) -(defparameter *framerate* 1000) -(defparameter *port* 21895) - -(ql:quickload :bordeaux-threads) -(ql:quickload :croatoan) -(ql:quickload :usocket) -(use-package :croatoan) -(use-package :usocket) - -(load "util.lisp") -(load "item-classes.lisp") -(load "item-methods.lisp") -(load "world.lisp") -(load "items.lisp") -(load "biomes.lisp") -(load "animals.lisp") -(load "server.lisp") -(load "client.lisp") +(in-package :naledi-ya-africa) +;(use-package :croatoan) (defun start-game () "Start the game logic and UI" + ;;FIXME revamp for networking (start-server) - (with-screen (scr :input-blocking *framerate* :enable-colors t - :input-echoing nil :cursor-visibility nil - :input-reading :unbuffered) + (croatoan:with-screen (scr :input-blocking *framerate* :enable-colors t + :input-echoing nil :cursor-visibility nil + :input-reading :unbuffered) (splash-screen scr) (user-interface scr))) (defun splash-screen (scr) "Display the splash screen with the `Naledi ya Africa' logo" - (let* ((width (.width scr)) (height (.height scr)) + (let* ((width (croatoan:.width scr)) (height (croatoan:.height scr)) (logo (load-text-file "LOGO")) (y (halve (- height (length logo)))) (xoff (halve (- width 80)))) - (clear scr) + (croatoan:clear scr) (dolist (l logo) - (move scr y xoff) - (add-string scr l) + (croatoan:move scr y xoff) + (croatoan:add-string scr l) (incf y)) - (move scr (1- height) 0) - (add-string scr "Press any key to continue.") - (move scr (1- height) (- width 22)) - (add-string scr "(c) 2018 Daniel Vedder") - (event-case (scr event) + (croatoan:move scr (1- height) 0) + (croatoan:add-string scr "Press any key to continue.") + (croatoan:move scr (1- height) (- width 22)) + (croatoan:add-string scr "(c) 2018 Daniel Vedder") + (croatoan:event-case (scr event) ((nil) nil) - (otherwise (return-from event-case))))) + (otherwise (return-from croatoan:event-case))))) (defun user-interface (scr) "Create the screen on the ncurses interface and hand over to window functions" - (let* ((width (.width scr)) (height (1- (.height scr))) + (let* ((width (croatoan:.width scr)) (height (1- (croatoan:.height scr))) (me (list (round (/ width 4)) (halve height)))) - (clear scr) - (refresh scr) - (with-windows ((mapwin :position '(0 0) :input-blocking *framerate* - :border t :width (- width 51) :height height) - (playerwin :position (list 0 (- width 50)) - :input-blocking *framerate* :border t - :width 50 :height (halve height 'down)) - (placewin :input-blocking *framerate* :border t - :position (list (halve height) (- width 50)) - :width 50 :height (halve height 'down)) - (newswin :input-blocking *framerate* - :position (list height 0) - :width width :height 1)) + (croatoan:clear scr) + (croatoan:refresh scr) + (croatoan:with-windows ((mapwin :position '(0 0) + :input-blocking *framerate* + :border t :width (- width 51) + :height height) + (playerwin :position (list 0 (- width 50)) + :input-blocking *framerate* :border t + :width 50 :height (halve height 'down)) + (placewin :input-blocking *framerate* + :border t + :position (list (halve height) + (- width 50)) + :width 50 :height (halve height 'down)) + (newswin :input-blocking *framerate* + :position (list height 0) + :width width :height 1)) (update-ui mapwin playerwin placewin newswin me) ;;TODO - (event-case (scr event) - (#\q (disconnect) (terminate) (return-from event-case)) ;XXX + (croatoan:event-case (scr event) + (#\q (disconnect) + (terminate) + (return-from croatoan:event-case)) ;XXX (#\n (draw-menu (message-window))) (:up (decf (second me)) (update-ui mapwin playerwin placewin newswin me)) @@ -100,63 +87,67 @@ (defun draw-map (win me) "Draw a portion of the game map in an ncurses window" - (setf (.color-pair win) '(:white :black)) - (box win) - (move win 1 1) - (let ((x0 (- (first me) (round (/ (.width win) 4)))) - (y0 (- (second me) (halve (.height win))))) + (setf (croatoan:.color-pair win) '(:white :black)) + (croatoan:box win) + (croatoan:move win 1 1) + (let ((x0 (- (first me) (round (/ (croatoan:.width win) 4)))) + (y0 (- (second me) (halve (croatoan:.height win))))) ;; NB. x0 and w are calculated differently to y0 and h because we insert ;; a space after each character - (dotimes (h (1- (.height win))) - (dotimes (w (- (halve (.width win) 'floor) 2)) + (dotimes (h (1- (croatoan:.height win))) + (dotimes (w (- (halve (croatoan:.width win) 'floor) 2)) (let ((p (coord (+ w x0 3) (+ h y0 1)))) - (if (null p) (add-char win #\space) + (if (null p) (croatoan:add-char win #\space) (if (and (= (first (patch-pos p)) (first me)) (= (second (patch-pos p)) (second me))) - (progn (setf (.color-pair win) '(:white :black)) - (add-char win #\@)) + (progn (setf (croatoan:.color-pair win) + '(:white :black)) + (croatoan:add-char win #\@)) (if (patch-occupant p) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (.color (patch-occupant p)) :black)) - (add-char win (.char (patch-occupant p)))) + (croatoan:add-char win + (.char (patch-occupant p)))) (progn - (setf (.color-pair win) + (setf (croatoan:.color-pair win) (list (biome-col (patch-biome p)) :black)) - (add-char win + (croatoan:add-char win (biome-char (patch-biome p))))))) - (add-char win #\space))) - (move win (1+ h) 1)) - (refresh win))) + (croatoan:add-char win #\space))) + (croatoan:move win (1+ h) 1)) + (croatoan:refresh win))) (defun draw-player-panel (win) "Draw a panel with information about the player character." ;;TODO - (box win) - (move win 1 1) - (add-string win "This is the player panel.") - (refresh win)) + (croatoan:box win) + (croatoan:move win 1 1) + (croatoan:add-string win "This is the player panel.") + (croatoan:refresh win)) (defun draw-place-panel (win me) "Draw a panel with information about the player's current location." - (let ((descr (break-lines (describe-patch me) (- (.width win) 2)))) - (clear win) - (box win) - (move win 1 1) + (let ((descr (break-lines (describe-patch me) + (- (croatoan:.width win) 2)))) + (croatoan:clear win) + (croatoan:box win) + (croatoan:move win 1 1) (dolist (d descr) - (add-string win d) - (move win (1+ (first (.cursor-position win))) 1)) - (refresh win))) + (croatoan:add-string win d) + (croatoan:move win + (1+ (first (croatoan:.cursor-position win))) 1)) + (croatoan:refresh win))) (let ((news '("Press h for help."))) (defun draw-news-panel (win) "Draw a thin panel at the bottom of the screen to display news items." - (clear win) - (move win 0 0) - (add-string win (car news)) - (refresh win)) + (croatoan:clear win) + (croatoan:move win 0 0) + (croatoan:add-string win (car news)) + (croatoan:refresh win)) (defun notify (news-string &rest formats) "Append a string to the news to notify the user." @@ -189,5 +180,3 @@ (defun process-command (event) ;;TODO ) - -;(start-game) diff --git a/package.lisp b/package.lisp new file mode 100644 index 0000000..164381d --- /dev/null +++ b/package.lisp @@ -0,0 +1,22 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This is file defines the naledi-ya-africa package. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(defpackage :naledi-ya-africa + (:documentation "A multiplayer survival game set in Africa.") + (:nicknames :naledi :nya) + (:use :common-lisp) + (:export + ;;XXX export anything else? + start-game + start-server + terminate + init-world + connect-server + query-server + disconnect)) diff --git a/player.lisp b/player.lisp deleted file mode 100644 index 5db362e..0000000 --- a/player.lisp +++ /dev/null @@ -1,78 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file is responsible for managing the player instance. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defparameter *player* NIL) - -(defstruct player - (name "") - (strength 0) - (dexterity 0) - (intelligence 0) - (experience 0) - (level 0) - (hunger 0) - (health 0) - (equipment NIL) - (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) - (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) - - -;; INVENTORY HANDLING FUNCTIONS - -(defun stock-size (resource &optional (player *player*)) - "How many items of this resource type is the player carrying?" - (dolist (i (player-inventory player) 0) - (when (eq (item-name (first i)) resource) - (return-from stock-size (second i))))) - -(defun weight-carried (&optional (player *player*)) - "Sum up the total weight of all items carried" - (+ (item-weight (player-equipment player)) - (reduce #'+ (mapcar #'(lambda (i) - (* (second i) (item-weight (first i))))) - (player-inventory player)))) - -(defun pickup (item &optional (player *player*)) - "Add the item object to the inventory" - ;;Can the item be picked up at all? - (unless (item-movable item) - (notify "This item cannot be picked up.") - (return-from pickup)) - ;;Is the player strong enough to pick this up? - (unless (<= (+ (item-weight item) (weight-carried player)) - (* (player-strength player) 20)) ;XXX magic number - (notify "You are too burdened to pick this up.") - (return-from pickup)) - (dolist (inv (player-inventory player)) - ;;Resources may be stacked - (when (and (item-resource item) - (eq (item-name item) (item-name (first inv)))) - (incf (second inv)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup)) - ;;Deposit the item in an empty slot - (when (and (null (first inv)) (item-resource item) ;normal pickup - ;;XXX replace with find-if - (zerop (count-instances (item-name item) - (mapcar #'(lambda (i) (item-name (car i))))))) - (setf inv (list item 1)) - (notify "You have picked up one ~A." (item-name item)) - (return-from pickup))) - ;;If nothing has worked, the inventory is full - (notify "Your inventory is full.")) - -(defun drop (inv-nr &optional (player *player)) - "Drop an item from the given inventory index" - ;;TODO add item back to patch - (let* ((item-entry (nth inv-nr (player-inventory player))) - (item (when item-entry (first item-entry)))) - (if (and item (> (stock-size (item-name item) 1))) - (decf (second item-entry)) - (setf item NIL (second item-entry) 0)))) - diff --git a/server.lisp b/server.lisp deleted file mode 100644 index 656ac10..0000000 --- a/server.lisp +++ /dev/null @@ -1,160 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file stores all game data and handles the server. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -;;XXX utility function during development, remove later -(defun dt (&optional (n 0)) - (bt:destroy-thread (nth n (bt:all-threads)))) - -;; TODO save and load functions -;; XXX Will probably require `make-load-form-saving-slots' - -;;; WORLD OBJECT - -(let ((world NIL)) - (defun set-world (w) (setf world w)) - - (defun world-size () (length world)) - - (defun save-topography (file-name) ;XXX (re)move this? - "Save the world topography as a text file" - (debugging "~&Saving world to file ~A" file-name) ;debug - (with-open-file (tf file-name :direction :output) - (dolist (row world) - (format stream "~&~A~%" - (string-from-list - (mapcar #'(lambda (p) - (biome-char (patch-biome p))) - row) ""))))) - - (defun save-world () - ;;TODO - ) - - (defun coord (x y) - "Return the patch at the given coordinates or NIL if out of bounds" - (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) - (nth x (nth y world))))) - -;;; WORLD THREADS - -(let ((uptime 0) (world-thread NIL) (server-thread NIL) - (player-threads NIL) (running NIL)) - - (defun start-server (&optional (force NIL)) - "Start the game server" - ;;TODO cannot restart -> ADDRESS-IN-USE ERROR - ;; -> comes from not closing connections properly? - ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? - (when force - (reset-server-threads) - (reset-world-age)) - (unless (or world-thread server-thread) - (init-world) - (setf running T) - (setf world-thread - (bt:make-thread #'update-loop :name "world-thread")) - (setf server-thread - (bt:make-thread #'run-server :name "server-thread")))) - - (defun terminate () - (notify "Terminating the world.") - (setf running NIL) - ;;XXX have to use destroy-thread because the server mostly idles, - ;; waiting for connections - only checks 'running' when connecting - (bt:destroy-thread server-thread) - (bt:join-thread world-thread) - (dolist (pt player-threads) - (bt:join-thread pt)) - (save-world)) ;XXX not yet implemented - - (defun age-of-the-world () uptime) - - (defun reset-world-age () (setf uptime 0)) - - (defun runningp () running) - - (defun reset-server-threads () - (set-list NIL server-thread world-thread player-threads)) - - (defun update-loop () - "The main loop, updating the world in the background" - ;;XXX split this up into two or more functions, to be run by - ;; different threads? - (logging "UPDATE ~S" uptime) - ;;Update all items and occupants in each patch - (dotimes (y (world-size)) - (dotimes (x (world-size)) - (unless running (return-from update-loop)) - (when (patch-occupant (coord x y)) - (update (patch-occupant (coord x y)))) - (dolist (i (patch-items (coord x y))) - (update i)))) - ;;Update all items each player has - ;;TODO - ;;Save the world and start over - (save-world) ;XXX not yet implemented - (incf uptime) - (sleep (/ *framerate* 1000)) - (when running (update-loop))) ;;requires Tail-Call Optimization - - (defun run-server () - "Start a server, listening for connections" - (with-socket-listener (socket "127.0.0.1" *port*) - (while running - (wait-for-input socket) - (let ((thread (bt:make-thread - #'(lambda () (handle-connection socket)) - :name (string-from-list (list "player-thread" - (length player-threads)) "-")))) - (setf player-threads (cons thread player-threads)) - (sleep 1))))) ;;give the socket a chance to connect - - (defun handle-connection (socket) - "Answer requests until the player disconnects" - (with-connected-socket (connection (socket-accept socket)) - (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' - (do* ((sockstr (socket-stream connection)) - (request (read-line sockstr NIL) (read-line sockstr NIL))) - ;;TODO remove player-thread from list when terminated - ((or (not running) (null request))) - (format sockstr "~S~%" (to-string (answer request))) - (finish-output sockstr))))) - -(defun answer (request) - (logging "SERVER: received request ~S" request) - (let* ((reqelts (extract-elements request)) - (player-name (first reqelts)) - (cmd (second reqelts)) - (args (cddr reqelts))) - (cond ((eq player-name 'ACK) "ACK ACK") ;debug - ((eq cmd 'get-map) (get-map player-name)) - ((eq cmd 'describe-patch) (describe-patch args)) - ;;TODO - ))) - -;;; COMMUNICATION FUNCTIONS - -(defun get-map (player-name) - "Return a 2d list of map places (each a list of a char and a colour)" - ;;TODO - ) - -(defun describe-patch (coords) - "Return a list of lines describing the patch at these coordinates." - (let ((p (coord (first coords) (second coords)))) - (list (string-upcase (biome-name (patch-biome p))) "" - (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" - (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) - (when (patch-occupant p) ;TODO players -> "$name is here." - (format NIL "There is ~A here." - (leading-vowel (.name (patch-occupant p))))) - (when (patch-items p) - (format NIL "The following items are here:~A *~A" #\newline - (string-from-list (mapcar #'.name (patch-items p))) - (format NIL "~% *")))))) diff --git a/src/client.lisp b/src/client.lisp new file mode 100644 index 0000000..01052cd --- /dev/null +++ b/src/client.lisp @@ -0,0 +1,42 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for connecting to the server and provides the +;;;; API functions for doing so. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(let ((naledi-server NIL)) + (defun connect-server (&optional (ip "127.0.0.1") (port *port*)) + "Connect to the specified server" + ;;FIXME I need to catch some exceptions here... + (setf naledi-server (usocket:socket-connect ip port)) + (if naledi-server + (notify "Connected to server ~A:~A" ip port) + (notify "Connection to server ~A:~A failed." ip port))) + + (defun current-server () naledi-server) ;TODO remove after development + + (defun query-server (request) + "Send a request string to the server and return the answer" + (unless naledi-server ;XXX do this with exceptions + (return-from query-server "You are not connected to a server!")) + (let ((servstr (usocket:socket-stream naledi-server))) + (logging "CLIENT: sending request ~S" request) + (format servstr "~A~%" request) + (finish-output servstr) + ;;FIXME server still doesn't receive string until disconnect... + (logging "CLIENT: waiting for server response") + (usocket:wait-for-input naledi-server) + (read-from-string (read-line servstr)))) + + (defun disconnect () + "Disconnect from the server" + (when naledi-server + (usocket:socket-close naledi-server) + (setf naledi-server NIL) + (notify "Disconnected from server.")))) diff --git a/src/item-classes.lisp b/src/item-classes.lisp new file mode 100644 index 0000000..2eda5b8 --- /dev/null +++ b/src/item-classes.lisp @@ -0,0 +1,75 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS classes used in the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defclass item () + ;; The base class of all game items. + ((name :accessor .name :initarg :name :initform "") + (description :accessor .description :initarg :description :initform "") + (weight :accessor .weight :initarg :weight :initform 0) + (movable :reader movablep :initarg :movable :initform T))) + +(defclass destructable (item) + ;; An item that can be destroyed + ((destroy-with :reader .destructors :initarg :destroy-with :initform NIL) + (health :accessor .health :initarg :health :initform 1) + (drops :reader .drops :initarg :drops :initform '()))) + +(defclass craftable (item) + ;; An item that can be crafted from other items or resources + ((requires-tool :reader requires-tool :initarg :requires-tool :initform '()) + (craft-with :reader craft-with :initarg :craft-with :initform '()))) + +(defclass feature (destructable) + ;; A landscape feature such as a rock, a tree, or an animal. + ((symbol-char :reader .char :initarg :char :initform NIL) + (symbol-color :reader .color :initarg :color :initform NIL) + (x-pos :accessor .x :initarg :x :initform 0) + (y-pos :accessor .y :initarg :y :initform 0)) + (:default-initargs :movable NIL)) + +(defclass resource (item) + ;; A resource that can be gathered and used to craft items. + ((burning-product :reader .burning-product :initarg :burning-product))) + +(defclass tool (craftable) + ;; A tool or weapon that can be crafted and used in-game. + ((level :reader .level :initarg :level :initform 0) + ;;TODO the type needs some thought (add/change to efficiency?) + (type :reader .type :initarg :type :initform NIL) ;e.g. 'weapon, 'wood + (condition :accessor .condition :initarg :condition :initform 100))) + +(defclass container (craftable feature) + ;; An item that can store other items (and perhaps manipulate them) + ((contains :accessor .contains :initform '()) + (max-weight :reader .max-weight :initarg :max-weight :initform -1) + (capacity :reader .capacity :initarg :capacity :initform 0))) + +(defclass animal (feature) + ;; An animal species + (;; species properties + (strength :accessor .strength :initarg :strength :initform 1) + (max-health :accessor .max-health :initarg :max-health :initform 1) + (aggression :accessor .aggression :initarg :aggression :initform 0) + (group-size :reader .group-size :initarg :group-size :initform 1) + (habitat :reader .habitat :initarg :habitat :initform '()) + ;; individual properties + (id :reader .id :initarg :id :initform -1) + (last-move :accessor .last-move :initform 0)) + (:default-initargs :destroy-with '(weapon) :movable T)) + +;;TODO (defclass player (feature) + +;; Create a new class for each item type +(defmacro new-item (superclass name &body body) + `(defclass ,name (,superclass) () + (:default-initargs ,@body + :name (string-downcase (to-string ',name))))) + diff --git a/src/item-methods.lisp b/src/item-methods.lisp new file mode 100644 index 0000000..cf9b60a --- /dev/null +++ b/src/item-methods.lisp @@ -0,0 +1,55 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines all CLOS methods used in the game for the different +;;;; item classes. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;; Default `update' and `action' methods are NOP +(defmethod update ((i item))) +(defmethod action ((i item))) + +(defmethod attack ((d destructable) (tl tool)) + "Attack a destructable item with a tool or weapon" + ;;Returns either the damaged item or the items it drops when destroyed + (if (member (class-of tl) (.destructors d)) + (if (>= 0 (decf (.health d) (random (* 10 (.level tl))))) + (.drops d) d) + (progn (notify "You cannot attack ~A with ~A." + (leading-vowl (.name d)) (leading-vowel (.name tl))) + NIL))) + +;;TODO (defmethod attack ((f feature) (tl tool))) +;;TODO (defmethod attack ((a animal) (w weapon))) + +(defmethod update ((a animal)) + (when (> (age-of-the-world) (.last-move a)) + (random-move a) + (incf (.last-move a)))) + +(defmethod random-move ((a animal)) + "Move in a random direction within the species' habitat niche" + (do* ((dir (random-elt *directions*) (random-elt *directions*)) + (next-patch (patchindir (.x a) (.y a) dir) + (patchindir (.x a) (.y a) dir)) + (ttl 10 (1- ttl))) + ((zerop ttl) + ;(logging "The ~A at ~S/~S didn't move." (.name a) (.x a) (.y a)) + NIL) + (when (and next-patch (null (patch-occupant next-patch)) + (member (read-from-string + (biome-name (patch-biome next-patch))) + (.habitat a))) + ;; (logging "The ~A at ~S/~S is moving ~S." + ;; (.name a) (.x a) (.y a) dir) + (setf (patch-occupant (coord (.x a) (.y a))) NIL) + (setf (.x a) (first (patch-pos next-patch)) + (.y a) (second (patch-pos next-patch))) + (setf (patch-occupant next-patch) a) + (return-from random-move a)))) + diff --git a/src/params.lisp b/src/params.lisp new file mode 100644 index 0000000..329904f --- /dev/null +++ b/src/params.lisp @@ -0,0 +1,29 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file contains all global parameters used by the game. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;Enable debugging statements +(defparameter *debugging* T) + +;;Specify the logfile +(defparameter *logfile* "../naledi.log") + +;;Size of the world (= length of one side of a square) +(defparameter *world-size* 250) + +;;Milliseconds between world updates and screen refreshes +(defparameter *framerate* 1000) + +;;Network port to run the server on +(defparameter *port* 21895) + +;;Compass directions needed for spatial functions +(defparameter *directions* '(N NE E SE S SW W NW)) + diff --git a/src/player.lisp b/src/player.lisp new file mode 100644 index 0000000..e4179c6 --- /dev/null +++ b/src/player.lisp @@ -0,0 +1,80 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file is responsible for managing the player instance. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defparameter *player* NIL) ;;TODO replace with player-list + +(defstruct player + (name "") + (strength 0) + (dexterity 0) + (intelligence 0) + (experience 0) + (level 0) + (hunger 0) + (health 0) + (equipment NIL) + (inventory '((NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0) + (NIL 0) (NIL 0) (NIL 0) (NIL 0) (NIL 0)))) + + +;; INVENTORY HANDLING FUNCTIONS + +(defun stock-size (resource &optional (player *player*)) + "How many items of this resource type is the player carrying?" + (dolist (i (player-inventory player) 0) + (when (eq (item-name (first i)) resource) + (return-from stock-size (second i))))) + +(defun weight-carried (&optional (player *player*)) + "Sum up the total weight of all items carried" + (+ (item-weight (player-equipment player)) + (reduce #'+ (mapcar #'(lambda (i) + (* (second i) (item-weight (first i))))) + (player-inventory player)))) + +(defun pickup (item &optional (player *player*)) + "Add the item object to the inventory" + ;;Can the item be picked up at all? + (unless (item-movable item) + (notify "This item cannot be picked up.") + (return-from pickup)) + ;;Is the player strong enough to pick this up? + (unless (<= (+ (item-weight item) (weight-carried player)) + (* (player-strength player) 20)) ;XXX magic number + (notify "You are too burdened to pick this up.") + (return-from pickup)) + (dolist (inv (player-inventory player)) + ;;Resources may be stacked + (when (and (item-resource item) + (eq (item-name item) (item-name (first inv)))) + (incf (second inv)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup)) + ;;Deposit the item in an empty slot + (when (and (null (first inv)) (item-resource item) ;normal pickup + ;;XXX replace with find-if + (zerop (count-instances (item-name item) + (mapcar #'(lambda (i) (item-name (car i))))))) + (setf inv (list item 1)) + (notify "You have picked up one ~A." (item-name item)) + (return-from pickup))) + ;;If nothing has worked, the inventory is full + (notify "Your inventory is full.")) + +(defun drop (inv-nr &optional (player *player)) + "Drop an item from the given inventory index" + ;;TODO add item back to patch + (let* ((item-entry (nth inv-nr (player-inventory player))) + (item (when item-entry (first item-entry)))) + (if (and item (> (stock-size (item-name item) 1))) + (decf (second item-entry)) + (setf item NIL (second item-entry) 0)))) + diff --git a/src/server.lisp b/src/server.lisp new file mode 100644 index 0000000..fa4d470 --- /dev/null +++ b/src/server.lisp @@ -0,0 +1,163 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file stores all game data and handles the server. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +;;XXX utility function during development, remove later +(defun dt (&optional (n 0)) + (bt:destroy-thread (nth n (bt:all-threads)))) + +;; TODO save and load functions +;; XXX Will probably require `make-load-form-saving-slots' + +;;; WORLD OBJECT + +(let ((world NIL)) + (defun set-world (w) (setf world w)) + + (defun world-size () (length world)) + + (defun save-topography (file-name) ;XXX (re)move this? + "Save the world topography as a text file" + (debugging "~&Saving world to file ~A" file-name) ;debug + (with-open-file (tf file-name :direction :output) + (dolist (row world) + (format stream "~&~A~%" + (string-from-list + (mapcar #'(lambda (p) + (biome-char (patch-biome p))) + row) ""))))) + + (defun save-world () + ;;TODO + ) + + (defun coord (x y) + "Return the patch at the given coordinates or NIL if out of bounds" + (unless (or (< x 0) (< y 0) (> x (length world)) (> y (length world))) + (nth x (nth y world))))) + +;;; WORLD THREADS + +(let ((uptime 0) (world-thread NIL) (server-thread NIL) + (player-threads NIL) (running NIL)) + + (defun start-server (&optional (force NIL)) + "Start the game server" + ;;TODO cannot restart -> ADDRESS-IN-USE ERROR + ;; -> comes from not closing connections properly? + ;;FIXME causes Naledi to hang on `q' -> Is this really the problem? + (when force + (reset-server-threads) + (reset-world-age)) + (unless (or world-thread server-thread) + (init-world) + (setf running T) + (setf world-thread + (bt:make-thread #'update-loop :name "world-thread")) + (setf server-thread + (bt:make-thread #'run-server :name "server-thread")))) + + (defun terminate () + (notify "Terminating the world.") + (setf running NIL) + ;;XXX have to use destroy-thread because the server mostly idles, + ;; waiting for connections - only checks 'running' when connecting + (bt:destroy-thread server-thread) + (bt:join-thread world-thread) + (dolist (pt player-threads) + (bt:join-thread pt)) + (reset-server-threads) + (save-world)) ;XXX not yet implemented + + (defun age-of-the-world () uptime) + + (defun reset-world-age () (setf uptime 0)) + + (defun runningp () running) + + (defun reset-server-threads () + (set-list NIL server-thread world-thread player-threads)) + + (defun update-loop () + "The main loop, updating the world in the background" + ;;XXX split this up into two or more functions, to be run by + ;; different threads? + (logging "UPDATE ~S" uptime) + ;;Update all items and occupants in each patch + (dotimes (y (world-size)) + (dotimes (x (world-size)) + (unless running (return-from update-loop)) + (when (patch-occupant (coord x y)) + (update (patch-occupant (coord x y)))) + (dolist (i (patch-items (coord x y))) + (update i)))) + ;;Update all items each player has + ;;TODO + ;;Save the world and start over + (save-world) ;XXX not yet implemented + (incf uptime) + (sleep (/ *framerate* 1000)) + (when running (update-loop))) ;;requires Tail-Call Optimization + + (defun run-server () + "Start a server, listening for connections" + (usocket:with-socket-listener (socket "127.0.0.1" *port*) + (while running + (usocket:wait-for-input socket) + (let ((thread (bt:make-thread + #'(lambda () (handle-connection socket)) + :name (string-from-list (list "player-thread" + (length player-threads)) "-")))) + (setf player-threads (cons thread player-threads)) + (sleep 1))))) ;;give the socket a chance to connect + + (defun handle-connection (socket) + "Answer requests until the player disconnects" + (usocket:with-connected-socket (conn (usocket:socket-accept socket)) + (logging "~&SERVER: received a connection.") ; cf. `get-peer-name' + (do* ((sockstr (usocket:socket-stream conn)) + (request (read-line sockstr NIL) (read-line sockstr NIL))) + ;;TODO remove player-thread from list when terminated + ((or (not running) (null request))) + (format sockstr "~S~%" (to-string (answer request))) + (finish-output sockstr))))) + +(defun answer (request) + (logging "SERVER: received request ~S" request) + (let* ((reqelts (extract-elements request)) + (player-name (first reqelts)) + (cmd (second reqelts)) + (args (cddr reqelts))) + (cond ((eq player-name 'ACK) "ACK ACK") ;debug + ((eq cmd 'get-map) (get-map player-name)) + ((eq cmd 'describe-patch) (describe-patch args)) + ;;TODO + ))) + +;;; COMMUNICATION FUNCTIONS + +(defun get-map (player-name) + "Return a 2d list of map places (each a list of a char and a colour)" + ;;TODO + ) + +(defun describe-patch (coords) + "Return a list of lines describing the patch at these coordinates." + (let ((p (coord (first coords) (second coords)))) + (list (string-upcase (biome-name (patch-biome p))) "" + (format NIL "~A / ~A" (first (patch-pos p)) (second (patch-pos p))) "" + (format NIL "The ground here is ~A." (biome-ground (patch-biome p))) + (when (patch-occupant p) ;TODO players -> "$name is here." + (format NIL "There is ~A here." + (leading-vowel (.name (patch-occupant p))))) + (when (patch-items p) + (format NIL "The following items are here:~A *~A" #\newline + (string-from-list (mapcar #'.name (patch-items p))) + (format NIL "~% *")))))) diff --git a/src/util.lisp b/src/util.lisp new file mode 100644 index 0000000..341e15c --- /dev/null +++ b/src/util.lisp @@ -0,0 +1,339 @@ +;;; +;;; This is a personal utility module that I use in most of my Common Lisp +;;; projects. It originated in the Atlantis project, but has been used many +;;; times since and evolved accordingly. +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 09/05/2015 +;;; + +(in-package :naledi-ya-africa) ;;XXX change to dv-utils? + +;;; MACROS + +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + +(defmacro logging (str &rest format-args) + "Write an entry to *logfile*" + ;;FIXME gives error if cwd != file directory + `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) + +(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))) + ((null vl) expr) + (setf (cdr (last expr)) (list var)) + (setf (cdr (last expr)) (list value)))) + +(defmacro input (&rest vars) + "Take input from terminal and store each element in a passed variable" + ;; XXX Add a prompt parameter again? + `(progn + (format t "~&>>> ") + (set-list (read) ,@vars) + (first (list ,@vars)))) + +(defmacro input-string (&optional (var (gensym))) + "Read a string input line" + `(progn + (format t "~&>>> ") + (setf ,var (read-line)) + ,var)) + +(defmacro while (condition &body body) + "An implementation of a while loop as found in other languages" + `(do () + ((not ,condition) NIL) + ,@body)) + +(defmacro != (object1 object2 &key (test 'eql)) + "A not-equals macro to save some typing" + `(not (,test ,object1 ,object2))) + +(defmacro cassoc (entry table &key (test '#'eql)) + "Returns (car (cdr (assoc entry table)))" + `(car (cdr (assoc ,entry ,table :test ,test)))) + +(defmacro safe-nth (index lst) + "Return (nth index lst), or NIL if index is out of range" + `(if (> ,index (1- (length ,lst))) + NIL (nth ,index ,lst))) + +(defmacro safe-aref (vector index) + "Return (aref vector index), but return NIL if out of range" + `(if (> ,index (1- (length ,vector))) + NIL (aref ,vector ,index))) + +(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) + "A macro analogous to dolist" + (let-gensyms (index) + `(do* ((,index 0 (1+ ,index)) + (,element (safe-aref ,vector ,index) + (safe-aref ,vector ,index))) + ((= ,index (length ,vector)) ,return-variable) + ,@body))) + +;;; FUNCTIONS + +;; Some of these functions are probably quite inefficient (lots of consing) + +(defun remove-first-if (fn lst) + ;;FIXME isn't this identical to `remove-if'? + "Remove the first element in a list that satisfies the given predicate" + (cond ((null lst) NIL) + ((funcall fn (car lst)) (cdr lst)) + (T (cons (car lst) (remove-first-if fn (cdr lst)))))) + +(defun average (&rest numbers) + "Compute the average of the given numbers" + (/ (reduce #'+ numbers) (length numbers))) + +(defun halve (n &optional (round-fn 'round)) + "Halve a given number and round it to an integer." + (let ((half (/ n 2))) + (cond ((eq round-fn 'up) (ceiling half)) + ((eq round-fn 'down) (floor half)) + (T (round half))))) + +(defun keys (assoc-list) + "Return a list of the keys in an association list" + (if (null assoc-list) NIL + (cons (car (car assoc-list)) (keys (cdr assoc-list))))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components :sep ""))) + +(defun symbol-to-string (sym) + "Convert a symbol to a string, exchanging dashes for spaces" + (string-from-list + (split-string (string-downcase (string sym)) #\-) #\space)) + +(defun letter-in-string (letter str) + "Is this letter in this string?" + (dotimes (i (length str) NIL) + (when (equalp (aref str i) letter) (return T)))) + +;; TODO change &optional to &key (and figure out why the heck that doesn't +;; work - clisp bug?), add null-filler keyword +(defun string-from-list (lst &optional (separator #\space)) + "Put all elements of lst into a single string, separated by the separator" + (cond ((null lst) "") + ((= (length lst) 1) (to-string (car lst))) + (T (concatenate 'string (to-string (first lst)) (to-string separator) + (string-from-list (cdr lst) separator))))) + +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + +(defun cut-string (s i) + "Cut string s in two at index i and return the two substrings in a list" + (if (or (minusp i) (> i (length s))) s + (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) + (dotimes (c (length s) (list s1 s2)) + (if (> i c) + (setf (aref s1 c) (aref s c)) + (setf (aref s2 (- c i)) (aref s c))))))) + +(defun char-list-to-string (char-list) + "Convert a character list to a string" + (let ((s (make-string (length char-list) :initial-element #\SPACE))) + (dotimes (i (length char-list) s) + (setf (aref s i) (nth i char-list))))) + +(defun trim-whitespace (s) + "Trim off spaces and tabs before and after string s" + (string-trim '(#\space #\tab) s)) + +(defun to-string (x) + "Whatever x is, convert it into a string" + (cond ((stringp x) x) + ((or (symbolp x) (characterp x)) (string x)) + (t (format NIL "~S" x)))) + +(defun leading-vowel (noun) + "Return noun prepended with 'a' or 'an', depending on its first letter." + (format NIL "~A ~A" + (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") + noun)) + +(defun break-lines (lines width &optional (indent "")) + "Take a list of lines and break any that are too long." + (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) + ((null ls) result) + (if (<= (length l) width) + (setf result (append result (list l))) + (setf ls (append (list nil (first (cut-string l width)) + (concatenate 'string (to-string indent) + (second (cut-string l width)))) + (cdr ls)))))) + +(defun extract-elements (str) + "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" + (multiple-value-bind (next-element i) (read-from-string str nil) + (if (null next-element) NIL + (cons next-element + (extract-elements (second (cut-string str i))))))) + +(defun count-instances (search-term search-sequence &key (test #'eql)) + "Count the number of instances of search-term in search-sequence" + (let ((count 0)) + (dotimes (i (length search-sequence) count) + (when (funcall test search-term (elt search-sequence i)) + (incf count))))) + +(defun most-common-element (lst &key (test #'eql)) + "Return the most common element in this list and how often it appears" + ;;This function has multiple return values! + ;;In case of multiple mces, return the one that appears first + (let ((elements-counted NIL) (max 0) (mce NIL)) + (dolist (e lst (values mce max)) + (unless (member e elements-counted :test test) + (let ((count (count-instances e lst :test test))) + (when (> count max) + (setf max count) + (setf mce e))) + (setf elements-counted (append elements-counted (list e))))))) + +(defun nths (n lst) + "Take in a list of lists and return the nth element of each" + (when (and lst (listp (car lst))) + (cons (nth n (car lst)) (nths n (cdr lst))))) + +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + +(defun range (stop &key (start 0) (step 1)) + "Return a list of numbers from start to stop" + ;;XXX Surely this must exist as a function in Common Lisp already, + ;; I just don't know what it's called... + (unless (>= start stop) + (cons start (range stop :start (+ start step) :step step)))) + +(defun to-list (vector &optional (next-elt 0)) + "Turn the vector into a list" + (if (= next-elt (1- (length vector))) NIL + (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) + +(defun cut-list (l i) + "Cut list l in two at index i and return the two sublists in a list" + (if (or (< i 1) (> i (length l))) l + (do* ((lst2 l (cdr lst2)) + (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) + ((= i (length lst1)) (list lst1 (cdr lst2)))))) + +(defun random-elt (seq) + "Return a random element of this sequence" + (elt seq (random (length seq)))) + +(defun random-offset (n max-offset) + "Return n plus a random offset" + (+ n (- (random (* 2 max-offset)) max-offset))) + +(defun chancep (p) + "Do a random test with 1/p probability of success." + (= 1 (random p))) + +(defun probabilityp (percent) + "Do a random test, with the percentage giving the success probability" + ;;Accuracy: 0.1 + (> percent (/ (random 1000) 10))) + +(defun load-text-file (file-name) + "Load a text file into a list of strings (representing the lines)" + (with-open-file (f file-name) + (do* ((line (read-line f nil nil) + (read-line f nil nil)) + (file-lines (list line) (append file-lines (list line)))) + ((null line) file-lines)))) + +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + +(defun write-to-file (text filename &optional (append NIL)) + "Write text (a string or list of strings) to the specified file" + (let ((text-list (if (listp text) text (list text))) + (f (if append + (open filename :direction :output + :if-exists :append :if-does-not-exist :create) + (open filename :direction :output :if-exists :supersede)))) + (dolist (line text-list) + (format f "~&~A~&" line)) + (close f))) + +(defun write-list (lst filename) + "Write a list to file, one entry per line." + (let ((f (open filename :direction :output))) + (dolist (i lst) (format f "~&~S" i)) + (close f))) + +(defun time-stamp (&optional (time-t (get-universal-time))) + (let ((time (multiple-value-list (decode-universal-time time-t)))) + (format NIL "~S/~S/~S ~S:~S" + (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) + +(defun build-symbol (&rest components) + "Concatenate the passed components into a single symbol" + (read-from-string (string-from-list components ""))) + +(defun make-list-function (container-type &optional (add-s t)) + "Return a function to return a list of the names of all objects of the +specified type in the container struct" + #'(lambda (object-type container) + (let* ((get-objects (symbol-function + (build-symbol container-type "-" + object-type (if add-s "s" "")))) + (get-object-name (symbol-function + (build-symbol object-type "-name"))) + (objects (funcall get-objects container)) (name-list NIL)) + (dolist (o objects name-list) + (when (stringp o) (return objects)) + (setf name-list + (cons (funcall get-object-name o) name-list)))))) + +;; DEPRECATED - replace with number-menu +(defun choose-number-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)) + +;; DEPRECATED - replace with number-menu +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +;; SUPERSEDES choose-number-option AND choose-option! +(defun number-menu (entries &optional (header "Please choose an option:")) + "Display a number-based menu and act on the user's choice" + ;; The param entries is a list of lists in the form + ;; ( ) + (format t "~&~A" header) + (dotimes (i (length entries)) + (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) + (format t "~&>>> ") + (setf choice (read)) + (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) + (format t "~&Invalid choice! Please choose again:~%>>> ") + (setf choice (read))) + (funcall (second (nth choice entries)))) diff --git a/src/world.lisp b/src/world.lisp new file mode 100644 index 0000000..5365554 --- /dev/null +++ b/src/world.lisp @@ -0,0 +1,187 @@ +;;;; +;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game +;;;; set in Africa. +;;;; +;;;; This file defines patches and administrates the world object. +;;;; +;;;; (c) 2018 Daniel Vedder, MIT license +;;;; + +(in-package :naledi-ya-africa) + +(defstruct patch + (pos '(0 0)) ;position + (biome NIL) + (items '()) + (occupant NIL)) + +(defstruct biome + (name "") + (ground "") + (features '()) ;an alist of possible features and their 1/p probabilities + (char #\.) ;default map display character + (col ':white)) ;default map display colour + +;; BIOME LIST + +(let ((biome-list NIL)) + (defun register-biome (symbol-name biome-object) + (setf biome-list (cons (list symbol-name biome-object) biome-list))) + + (defun available-biomes () + (keys biome-list)) + + (defun get-biome (symbol-name) + (cassoc symbol-name biome-list))) + +(defmacro new-biome (name &body body) + `(register-biome ',name + (make-biome + :name ,(symbol-to-string name) + ,@body))) + +;; MATRIX FUNCTIONS + +(defun init-matrix (size) + "Create a square matrix of empty patches" + ;;TODO change this to arrays for performance + (debugging "~&Creating a ~S/~S matrix." size size) + (do ((y 0 (1+ y)) (world NIL) (row NIL NIL)) + ((= y size) world) + (dotimes (x size) + (setf row (append row (list (make-patch :pos (list x y)))))) + (setf world (append world (list row))))) + +(defun distance (x1 y1 x2 y2 &optional (pythag NIL)) + "Find the distance between two sets of coordinates" + (if pythag (round (sqrt (+ (expt (- x1 x2) 2) (expt (- y1 y2) 2)))) + (min (abs (- x1 x2)) (abs (- y1 y2))))) + +(defun closest-coords (here coord-list &optional (abs-dist NIL)) + "Find the closest position to 'here' from a list of coordinates" + (do* ((clist coord-list (cdr clist)) (c (car clist) (car clist)) + (dist (when c (distance (first here) (second here) + (first c) (second c) abs-dist)) + (when c (distance (first here) (second here) + (first c) (second c) abs-dist))) + (mindist dist) (closest c)) + ((null clist) closest) + (when (< dist mindist) + (setf mindist dist closest c)))) + +(defun opposite-dir (dir) + "Return the direction opposite the input" + (let ((pos (position dir *directions*))) + (when pos (nth (rem (+ 4 pos) 8) *directions*)))) + +(defun next-dir (dir &optional (cw T)) + "Get the neighbouring direction (clockwise or anticlockwise)" + (let ((pos (position dir *directions*)) + (diff (if cw 1 -1))) + (when pos (nth (rem (+ diff pos 8) 8) *directions*)))) + +(defun orth-dir (dir &optional (cw T)) + "Get the direction orthogonal (at right angles) to the given one." + (next-dir (next-dir dir cw) cw)) + +(defun diagonalp (dir) + "Is dir a diagonal direction?" + (member dir '(NE SE SW NW) :test #'eq)) + +(defun dir2patch (herex herey therex therey) + "Calculate the direction to a patch" + (cond ((> herex therex) + (cond ((> herey therey) 'NW) + ((< herey therey) 'SW) + (T 'W))) + ((< herex therex) + (cond ((> herey therey) 'NE) + ((< herey therey) 'SE) + (T 'E))) + (T (cond ((> herey therey) 'N) + ((< herey therey) 'S) + (T NIL))))) + +(defun coordsindir (x y dir) + "Return the coordinates in the given direction" + (cond ((eq dir 'N) (list x (1- y))) + ((eq dir 'NE) (list (1+ x) (1- y))) + ((eq dir 'E) (list (1+ x) y)) + ((eq dir 'SE) (list (1+ x) (1+ y))) + ((eq dir 'S) (list x (1+ y))) + ((eq dir 'SW) (list (1- x) (1+ y))) + ((eq dir 'W) (list (1- x) y)) + ((eq dir 'NW) (list (1- x) (1- y))) + ((null dir) (list x y)) + (T (error "~&Invalid direction ~S")))) + +(defun patchindir (x y dir) + "Return the patch in the given direction" + (let* ((coords (coordsindir x y dir)) + (nextx (first coords)) (nexty (second coords))) + (coord nextx nexty))) + +(defun neighbour (p dir) + "Return the neighbouring patch in this direction" + (patchindir (first (patch-pos p)) (second (patch-pos p)) dir)) + + +;; WORLD CREATION FUNCTIONS + +(defun get-patch-feature (patch) + "Find a random feature (or none) to occupy this patch." + (let ((flist (biome-features (patch-biome patch)))) + (dolist (f flist NIL) + (when (chancep (second f)) + (return-from get-patch-feature + (make-instance (first f) :x (first (patch-pos patch)) + :y (second (patch-pos patch)))))))) + +(defun generate-biomes (size-factor) + ;;XXX The maps this produces don't look quite as expected, but for + ;; current purposes they are good enough + (debugging "~&Generating biomes") ;debug + (let* ((wsize (world-size)) (seeds NIL) + (nseeds (round (/ wsize size-factor))) + (biomes (remove-first-if + #'(lambda (e) (eq e 'stream)) + (available-biomes)))) + ;;Initialize a set of biome 'seed' coordinates + (dotimes (n nseeds) + (setf seeds + (cons (list (random wsize) + (random wsize) + (random-elt biomes)) + seeds))) + (debugging "~&~S" seeds) + ;;For each patch, calculate the closest seed and set to that biome + (dotimes (x wsize seeds) + (dotimes (y wsize) + (let ((p (coord x y)) + (b (third (closest-coords (list x y) seeds T)))) + (setf (patch-biome p) (get-biome b)) + (setf (patch-occupant p) (get-patch-feature p))))))) + +(defun generate-stream (x0 y0) + (debugging "~&Generating a stream, starting at ~S/~S" x0 y0) ;debug + (do* ((dir (random-elt *directions*) + (if (probabilityp 75) dir (next-dir dir (random-elt '(T NIL))))) + (patch (coord x0 y0) (neighbour patch dir))) + ((or (null patch) (eq (patch-biome patch) (get-biome 'stream)))) + (setf (patch-biome patch) (get-biome 'stream)) + (setf (patch-occupant patch) NIL))) + +(defun create-world (size) + "Create a world of the specified size (square)" + (set-world (init-matrix size)) + ;;XXX magic numbers + (generate-biomes 10) + (dotimes (s (round (/ (expt size 2) 2000))) + (generate-stream (random size) (random size)))) + +(defun init-world () + "Initialize the log, RNG, and world." + (write-to-file "NALEDI ya AFRICA" *logfile*) + (logging (time-stamp)) + (setf *random-state* (make-random-state t)) + (create-world *world-size*)) diff --git a/util.lisp b/util.lisp deleted file mode 100644 index 3d992b7..0000000 --- a/util.lisp +++ /dev/null @@ -1,336 +0,0 @@ -;;; -;;; This is a personal utility module that I use in most of my Common Lisp -;;; projects. It originated in the Atlantis project, but has been used many -;;; times since and evolved accordingly. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 09/05/2015 -;;; - -;;; MACROS - -(defmacro debugging (str &rest format-args) - "If *debugging* is true, print str" - `(when *debugging* (format t ,str ,@format-args))) - -(defmacro logging (str &rest format-args) - "Write an entry to *logfile*" - `(write-to-file (format NIL ,str ,@format-args) *logfile* T)) - -(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))) - ((null vl) expr) - (setf (cdr (last expr)) (list var)) - (setf (cdr (last expr)) (list value)))) - -(defmacro input (&rest vars) - "Take input from terminal and store each element in a passed variable" - ;; XXX Add a prompt parameter again? - `(progn - (format t "~&>>> ") - (set-list (read) ,@vars) - (first (list ,@vars)))) - -(defmacro input-string (&optional (var (gensym))) - "Read a string input line" - `(progn - (format t "~&>>> ") - (setf ,var (read-line)) - ,var)) - -(defmacro while (condition &body body) - "An implementation of a while loop as found in other languages" - `(do () - ((not ,condition) NIL) - ,@body)) - -(defmacro != (object1 object2 &key (test 'eql)) - "A not-equals macro to save some typing" - `(not (,test ,object1 ,object2))) - -(defmacro cassoc (entry table &key (test #'eql)) - "Returns (car (cdr (assoc entry table)))" - `(car (cdr (assoc ,entry ,table :test ,test)))) - -(defmacro safe-nth (index lst) - "Return (nth index lst), or NIL if index is out of range" - `(if (> ,index (1- (length ,lst))) - NIL (nth ,index ,lst))) - -(defmacro safe-aref (vector index) - "Return (aref vector index), but return NIL if out of range" - `(if (> ,index (1- (length ,vector))) - NIL (aref ,vector ,index))) - -(defmacro dovector ((element vector &optional (return-variable NIL)) &body body) - "A macro analogous to dolist" - (let-gensyms (index) - `(do* ((,index 0 (1+ ,index)) - (,element (safe-aref ,vector ,index) - (safe-aref ,vector ,index))) - ((= ,index (length ,vector)) ,return-variable) - ,@body))) - -;;; FUNCTIONS - -;; Some of these functions are probably quite inefficient (lots of consing) - -(defun remove-first-if (fn lst) - ;;FIXME isn't this identical to `remove-if'? - "Remove the first element in a list that satisfies the given predicate" - (cond ((null lst) NIL) - ((funcall fn (car lst)) (cdr lst)) - (T (cons (car lst) (remove-first-if fn (cdr lst)))))) - -(defun average (&rest numbers) - "Compute the average of the given numbers" - (/ (reduce #'+ numbers) (length numbers))) - -(defun halve (n &optional (round-fn 'round)) - "Halve a given number and round it to an integer." - (let ((half (/ n 2))) - (cond ((eq round-fn 'up) (ceiling half)) - ((eq round-fn 'down) (floor half)) - (T (round half))))) - -(defun keys (assoc-list) - "Return a list of the keys in an association list" - (if (null assoc-list) NIL - (cons (car (car assoc-list)) (keys (cdr assoc-list))))) - -(defun build-symbol (&rest components) - "Concatenate the passed components into a single symbol" - (read-from-string (string-from-list components :sep ""))) - -(defun symbol-to-string (sym) - "Convert a symbol to a string, exchanging dashes for spaces" - (string-from-list - (split-string (string-downcase (string sym)) #\-) #\space)) - -(defun letter-in-string (letter str) - "Is this letter in this string?" - (dotimes (i (length str) NIL) - (when (equalp (aref str i) letter) (return T)))) - -;; TODO change &optional to &key (and figure out why the heck that doesn't -;; work - clisp bug?), add null-filler keyword -(defun string-from-list (lst &optional (separator #\space)) - "Put all elements of lst into a single string, separated by the separator" - (cond ((null lst) "") - ((= (length lst) 1) (to-string (car lst))) - (T (concatenate 'string (to-string (first lst)) (to-string separator) - (string-from-list (cdr lst) separator))))) - -(defun split-string (str separator) - "Split the string up into a list of strings along the separator character" - (cond ((equalp str (to-string separator)) NIL) - ((zerop (count-instances separator str)) (list str)) - (T (let ((split-elt (cut-string str (position separator str)))) - (cons (first split-elt) - (split-string (second (cut-string (second split-elt) 1)) - separator)))))) - -(defun cut-string (s i) - "Cut string s in two at index i and return the two substrings in a list" - (if (or (minusp i) (> i (length s))) s - (let ((s1 (make-string i)) (s2 (make-string (- (length s) i)))) - (dotimes (c (length s) (list s1 s2)) - (if (> i c) - (setf (aref s1 c) (aref s c)) - (setf (aref s2 (- c i)) (aref s c))))))) - -(defun char-list-to-string (char-list) - "Convert a character list to a string" - (let ((s (make-string (length char-list) :initial-element #\SPACE))) - (dotimes (i (length char-list) s) - (setf (aref s i) (nth i char-list))))) - -(defun trim-whitespace (s) - "Trim off spaces and tabs before and after string s" - (string-trim '(#\space #\tab) s)) - -(defun to-string (x) - "Whatever x is, convert it into a string" - (cond ((stringp x) x) - ((or (symbolp x) (characterp x)) (string x)) - (t (format NIL "~S" x)))) - -(defun leading-vowel (noun) - "Return noun prepended with 'a' or 'an', depending on its first letter." - (format NIL "~A ~A" - (if (member (char noun 0) '(#\a #\e #\i #\o #\u)) "an" "a") - noun)) - -(defun break-lines (lines width &optional (indent "")) - "Take a list of lines and break any that are too long." - (do* ((ls lines (cdr ls)) (l (car ls) (car ls)) (result NIL)) - ((null ls) result) - (if (<= (length l) width) - (setf result (append result (list l))) - (setf ls (append (list nil (first (cut-string l width)) - (concatenate 'string (to-string indent) - (second (cut-string l width)))) - (cdr ls)))))) - -(defun extract-elements (str) - "Extract all Lisp elements (strings, symbols, numbers, etc.) from str" - (multiple-value-bind (next-element i) (read-from-string str nil) - (if (null next-element) NIL - (cons next-element - (extract-elements (second (cut-string str i))))))) - -(defun count-instances (search-term search-sequence &key (test #'eql)) - "Count the number of instances of search-term in search-sequence" - (let ((count 0)) - (dotimes (i (length search-sequence) count) - (when (funcall test search-term (elt search-sequence i)) - (incf count))))) - -(defun most-common-element (lst &key (test #'eql)) - "Return the most common element in this list and how often it appears" - ;;This function has multiple return values! - ;;In case of multiple mces, return the one that appears first - (let ((elements-counted NIL) (max 0) (mce NIL)) - (dolist (e lst (values mce max)) - (unless (member e elements-counted :test test) - (let ((count (count-instances e lst :test test))) - (when (> count max) - (setf max count) - (setf mce e))) - (setf elements-counted (append elements-counted (list e))))))) - -(defun nths (n lst) - "Take in a list of lists and return the nth element of each" - (when (and lst (listp (car lst))) - (cons (nth n (car lst)) (nths n (cdr lst))))) - -(defun set-p (lst) - "Is lst a set (i.e. no elements occur more than once)?" - (cond ((null lst) T) - ((member (car lst) (cdr lst)) NIL) - (T (set-p (cdr lst))))) - -(defun range (stop &key (start 0) (step 1)) - "Return a list of numbers from start to stop" - ;;XXX Surely this must exist as a function in Common Lisp already, - ;; I just don't know what it's called... - (unless (>= start stop) - (cons start (range stop :start (+ start step) :step step)))) - -(defun to-list (vector &optional (next-elt 0)) - "Turn the vector into a list" - (if (= next-elt (1- (length vector))) NIL - (cons (aref vector next-elt) (to-list vector (1+ next-elt))))) - -(defun cut-list (l i) - "Cut list l in two at index i and return the two sublists in a list" - (if (or (< i 1) (> i (length l))) l - (do* ((lst2 l (cdr lst2)) - (lst1 (list (car lst2)) (append lst1 (list (car lst2))))) - ((= i (length lst1)) (list lst1 (cdr lst2)))))) - -(defun random-elt (seq) - "Return a random element of this sequence" - (elt seq (random (length seq)))) - -(defun random-offset (n max-offset) - "Return n plus a random offset" - (+ n (- (random (* 2 max-offset)) max-offset))) - -(defun chancep (p) - "Do a random test with 1/p probability of success." - (= 1 (random p))) - -(defun probabilityp (percent) - "Do a random test, with the percentage giving the success probability" - ;;Accuracy: 0.1 - (> percent (/ (random 1000) 10))) - -(defun load-text-file (file-name) - "Load a text file into a list of strings (representing the lines)" - (with-open-file (f file-name) - (do* ((line (read-line f nil nil) - (read-line f nil nil)) - (file-lines (list line) (append file-lines (list line)))) - ((null line) file-lines)))) - -(defun print-text-file (file-name) - "Print out the contents of this text file" - (dolist (line (load-text-file file-name)) - (unless (null line) (format t "~%~A" line)))) - -(defun write-to-file (text filename &optional (append NIL)) - "Write text (a string or list of strings) to the specified file" - (let ((text-list (if (listp text) text (list text))) - (f (if append - (open filename :direction :output - :if-exists :append :if-does-not-exist :create) - (open filename :direction :output :if-exists :supersede)))) - (dolist (line text-list) - (format f "~&~A~&" line)) - (close f))) - -(defun write-list (lst filename) - "Write a list to file, one entry per line." - (let ((f (open filename :direction :output))) - (dolist (i lst) (format f "~&~S" i)) - (close f))) - -(defun time-stamp (&optional (time-t (get-universal-time))) - (let ((time (multiple-value-list (decode-universal-time time-t)))) - (format NIL "~S/~S/~S ~S:~S" - (nth 3 time) (nth 4 time) (nth 5 time) (nth 2 time) (nth 1 time)))) - -(defun build-symbol (&rest components) - "Concatenate the passed components into a single symbol" - (read-from-string (string-from-list components ""))) - -(defun make-list-function (container-type &optional (add-s t)) - "Return a function to return a list of the names of all objects of the -specified type in the container struct" - #'(lambda (object-type container) - (let* ((get-objects (symbol-function - (build-symbol container-type "-" - object-type (if add-s "s" "")))) - (get-object-name (symbol-function - (build-symbol object-type "-name"))) - (objects (funcall get-objects container)) (name-list NIL)) - (dolist (o objects name-list) - (when (stringp o) (return objects)) - (setf name-list - (cons (funcall get-object-name o) name-list)))))) - -;; DEPRECATED - replace with number-menu -(defun choose-number-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)) - -;; DEPRECATED - replace with number-menu -(defun choose-option (option-list) - "Like choose-number-option, but return the value of the choice" - ;; Basically just a utility wrapper - (nth (choose-number-option option-list) option-list)) - -;; SUPERSEDES choose-number-option AND choose-option! -(defun number-menu (entries &optional (header "Please choose an option:")) - "Display a number-based menu and act on the user's choice" - ;; The param entries is a list of lists in the form - ;; ( ) - (format t "~&~A" header) - (dotimes (i (length entries)) - (format t "~&~S) ~A" (1+ i) (first (nth i entries)))) - (format t "~&>>> ") - (setf choice (read)) - (while (or (not (numberp choice)) (> choice (length entries)) (< choice 1)) - (format t "~&Invalid choice! Please choose again:~%>>> ") - (setf choice (read))) - (funcall (second (nth choice entries)))) diff --git a/world.lisp b/world.lisp deleted file mode 100644 index ddd1f32..0000000 --- a/world.lisp +++ /dev/null @@ -1,187 +0,0 @@ -;;;; -;;;; Naledi ya Africa ("Star of Africa") is an ncurses-based survival game -;;;; set in Africa. -;;;; -;;;; This file defines patches and administrates the world object. -;;;; -;;;; (c) 2018 Daniel Vedder, MIT license -;;;; - -(defconstant *directions* '(N NE E SE S SW W NW)) - -(defstruct patch - (pos '(0 0)) ;position - (biome NIL) - (items '()) - (occupant NIL)) - -(defstruct biome - (name "") - (ground "") - (features '()) ;an alist of possible features and their 1/p probabilities - (char #\.) ;default map display character - (col ':white)) ;default map display colour - -;; BIOME LIST - -(let ((biome-list NIL)) - (defun register-biome (symbol-name biome-object) - (setf biome-list (cons (list symbol-name biome-object) biome-list))) - - (defun available-biomes () - (keys biome-list)) - - (defun get-biome (symbol-name) - (cassoc symbol-name biome-list))) - -(defmacro new-biome (name &body body) - `(register-biome ',name - (make-biome - :name ,(symbol-to-string name) - ,@body))) - -;; MATRIX FUNCTIONS - -(defun init-matrix (size) - "Create a square matrix of empty patches" - ;;TODO change this to arrays for performance - (debugging "~&Creating a ~S/~S matrix." size size) - (do ((y 0 (1+ y)) (world NIL) (row NIL NIL)) - ((= y size) world) - (dotimes (x size) - (setf row (append row (list (make-patch :pos (list x y)))))) - (setf world (append world (list row))))) - -(defun distance (x1 y1 x2 y2 &optional (pythag NIL)) - "Find the distance between two sets of coordinates" - (if pythag (round (sqrt (+ (expt (- x1 x2) 2) (expt (- y1 y2) 2)))) - (min (abs (- x1 x2)) (abs (- y1 y2))))) - -(defun closest-coords (here coord-list &optional (abs-dist NIL)) - "Find the closest position to 'here' from a list of coordinates" - (do* ((clist coord-list (cdr clist)) (c (car clist) (car clist)) - (dist (when c (distance (first here) (second here) - (first c) (second c) abs-dist)) - (when c (distance (first here) (second here) - (first c) (second c) abs-dist))) - (mindist dist) (closest c)) - ((null clist) closest) - (when (< dist mindist) - (setf mindist dist closest c)))) - -(defun opposite-dir (dir) - "Return the direction opposite the input" - (let ((pos (position dir *directions*))) - (when pos (nth (rem (+ 4 pos) 8) *directions*)))) - -(defun next-dir (dir &optional (cw T)) - "Get the neighbouring direction (clockwise or anticlockwise)" - (let ((pos (position dir *directions*)) - (diff (if cw 1 -1))) - (when pos (nth (rem (+ diff pos 8) 8) *directions*)))) - -(defun orth-dir (dir &optional (cw T)) - "Get the direction orthogonal (at right angles) to the given one." - (next-dir (next-dir dir cw) cw)) - -(defun diagonalp (dir) - "Is dir a diagonal direction?" - (member dir '(NE SE SW NW) :test #'eq)) - -(defun dir2patch (herex herey therex therey) - "Calculate the direction to a patch" - (cond ((> herex therex) - (cond ((> herey therey) 'NW) - ((< herey therey) 'SW) - (T 'W))) - ((< herex therex) - (cond ((> herey therey) 'NE) - ((< herey therey) 'SE) - (T 'E))) - (T (cond ((> herey therey) 'N) - ((< herey therey) 'S) - (T NIL))))) - -(defun coordsindir (x y dir) - "Return the coordinates in the given direction" - (cond ((eq dir 'N) (list x (1- y))) - ((eq dir 'NE) (list (1+ x) (1- y))) - ((eq dir 'E) (list (1+ x) y)) - ((eq dir 'SE) (list (1+ x) (1+ y))) - ((eq dir 'S) (list x (1+ y))) - ((eq dir 'SW) (list (1- x) (1+ y))) - ((eq dir 'W) (list (1- x) y)) - ((eq dir 'NW) (list (1- x) (1- y))) - ((null dir) (list x y)) - (T (error "~&Invalid direction ~S")))) - -(defun patchindir (x y dir) - "Return the patch in the given direction" - (let* ((coords (coordsindir x y dir)) - (nextx (first coords)) (nexty (second coords))) - (coord nextx nexty))) - -(defun neighbour (p dir) - "Return the neighbouring patch in this direction" - (patchindir (first (patch-pos p)) (second (patch-pos p)) dir)) - - -;; WORLD CREATION FUNCTIONS - -(defun get-patch-feature (patch) - "Find a random feature (or none) to occupy this patch." - (let ((flist (biome-features (patch-biome patch)))) - (dolist (f flist NIL) - (when (chancep (second f)) - (return-from get-patch-feature - (make-instance (first f) :x (first (patch-pos patch)) - :y (second (patch-pos patch)))))))) - -(defun generate-biomes (size-factor) - ;;XXX The maps this produces don't look quite as expected, but for - ;; current purposes they are good enough - (debugging "~&Generating biomes") ;debug - (let* ((wsize (world-size)) (seeds NIL) - (nseeds (round (/ wsize size-factor))) - (biomes (remove-first-if - #'(lambda (e) (eq e 'stream)) - (available-biomes)))) - ;;Initialize a set of biome 'seed' coordinates - (dotimes (n nseeds) - (setf seeds - (cons (list (random wsize) - (random wsize) - (random-elt biomes)) - seeds))) - (debugging "~&~S" seeds) - ;;For each patch, calculate the closest seed and set to that biome - (dotimes (x wsize seeds) - (dotimes (y wsize) - (let ((p (coord x y)) - (b (third (closest-coords (list x y) seeds T)))) - (setf (patch-biome p) (get-biome b)) - (setf (patch-occupant p) (get-patch-feature p))))))) - -(defun generate-stream (x0 y0) - (debugging "~&Generating a stream, starting at ~S/~S" x0 y0) ;debug - (do* ((dir (random-elt *directions*) - (if (probabilityp 75) dir (next-dir dir (random-elt '(T NIL))))) - (patch (coord x0 y0) (neighbour patch dir))) - ((or (null patch) (eq (patch-biome patch) (get-biome 'stream)))) - (setf (patch-biome patch) (get-biome 'stream)) - (setf (patch-occupant patch) NIL))) - -(defun create-world (size) - "Create a world of the specified size (square)" - (set-world (init-matrix size)) - ;;XXX magic numbers - (generate-biomes 10) - (dotimes (s (round (/ (expt size 2) 2000))) - (generate-stream (random size) (random size)))) - -(defun init-world () - "Initialize the log, RNG, and world." - (write-to-file "NALEDI ya AFRICA" *logfile*) - (logging (time-stamp)) - (setf *random-state* (make-random-state t)) - (create-world *world-size*))