More documentation; code organisation; started work on launcher.

This commit is contained in:
Simon Brooke 2024-04-22 21:53:21 +01:00
parent 9490c9fd3e
commit 96d61e7116
75 changed files with 540 additions and 80 deletions

View file

@ -1,9 +0,0 @@
(ns cc.journeyman.the-great-game.character.character
"A character that can talk; either human or dragon (although very probably
we won't do talking dragons until really well into this process). All
characters have the news-passing abilities of a gossip, but we use `gossip`
to mean a special character who is part of the news-passing network."
(:require [cc.journeyman.the-great-game.gossip.gossip :refer [dialogue]]
[cc.journeyman.the-great-game.agent.agent :refer [Agent]]))

View file

@ -69,61 +69,63 @@
(defrecord Agent
;; "A default agent."
[name craft home culture]
ProtoObject
ProtoContainer
;; ProtoObject
;; ProtoContainer
(contents
"The `contents` of an actor are the contents of their pack(s) (if any), where
a pack may be any sort of bag or container which the actor could reasonably
be carrying."
[actor]
(flatten
(map contents (filter #(satisfies? ProtoContainer %)
(:burden actor)))))
;; (contents
;; "The `contents` of an actor are the contents of their pack(s) (if any), where
;; a pack may be any sort of bag or container which the actor could reasonably
;; be carrying."
;; [actor]
;; (flatten
;; (map contents (filter #(satisfies? ProtoContainer %)
;; (:burden actor)))))
(is-empty?
[actor]
(empty? (:burden actor)))
;; (is-empty?
;; [actor]
;; (empty? (:burden actor)))
ProtoAgent
;; ProtoAgent
(act
Return a map in which :world is bound to a world like this `world `except that this `actor `has acted in it; and `:actor` is bound to an
actor like this `actor `except modified by the consequences of the action.
Circle indicates which activation circle the actor is in.
;; (act
;; “Return a map in which :world is bound to a world like this `world `except that this `actor `has acted in it; and `:actor` is bound to an
;; actor like this `actor `except modified by the consequences of the action.
;; Circle indicates which activation circle the actor is in.
Note that this implies that a `plan `is a function of three arguments, an
actor, a world. and a circle, and returns exactly the sort of map this
function returns.
[actor world circle]
(let [urgent (case circle
:other (cond
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle))
:background (cond
(threatened? actor world circle)
(plan-fight-or-flight actor world circle)
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle))
;; else
(cond
(threatened? actor world circle)
(plan-fight-or-flight actor world circle)
(hungry? actor world circle)
(plan-find-food actor world circle)
(tired? actor world circle)
(plan-find-rest actor world circle)
(pending-scheduled-action? actor world circle)
(plan-scheduled-action actor world circle)))
next-action (cond urgent urgent
(empty? (:plans actor))
(plan-goal actor world circle)
:else (first (:plans actor)))
consequences (apply next-action (list actor world circle))]
;; we return consequences of the action, except that, if the action
;; was on the plans of the actor, we remove it.
(if-not (= next-action (first (:plans actor)))
consequences
(assoc consequences :actor
(assoc (:actor consequences) :plans
(rest (-> consequences :actor :plans))))))))
;; Note that this implies that a `plan `is a function of three arguments, an
;; actor, a world. and a circle, and returns exactly the sort of map this
;; function returns.”
;; [actor world circle]
;; (let [urgent (case circle
;; :other (cond
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle))
;; :background (cond
;; (threatened? actor world circle)
;; (plan-fight-or-flight actor world circle)
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle))
;; ;; else
;; (cond
;; (threatened? actor world circle)
;; (plan-fight-or-flight actor world circle)
;; (hungry? actor world circle)
;; (plan-find-food actor world circle)
;; (tired? actor world circle)
;; (plan-find-rest actor world circle)
;; (pending-scheduled-action? actor world circle)
;; (plan-scheduled-action actor world circle)))
;; next-action (cond urgent urgent
;; (empty? (:plans actor))
;; (plan-goal actor world circle)
;; :else (first (:plans actor)))
;; consequences (apply next-action (list actor world circle))]
;; ;; we return consequences of the action, except that, if the action
;; ;; was on the plans of the actor, we remove it.
;; (if-not (= next-action (first (:plans actor)))
;; consequences
;; (assoc consequences :actor
;; (assoc (:actor consequences) :plans
;; (rest (-> consequences :actor :plans)))))))
)

View file

@ -0,0 +1,114 @@
(ns cc.journeyman.the-great-game.character.character
"A character that can talk; either human or dragon (although very probably
we won't do talking dragons until really well into this process). All
characters have the news-passing abilities of a gossip, but we use `gossip`
to mean a special character who is part of the news-passing network."
(:require [cc.journeyman.the-great-game.gossip.gossip :refer [dialogue]]
[cc.journeyman.the-great-game.agent.agent :refer [ProtoAgent]]
[cc.journeyman.the-great-game.character.container :refer [ProtoContainer]]
[clojure.string :as cs :only [join]])
(:import [clojure.lang IPersistentMap]))
(defn honorific
"Placeholder. If a character is a teir one craftsman, they get called 'Master';
if a teir two ariston, they get called 'Ariston' and if a teir one ariston,
'Tyrranos'. But the logic of this is about occupations, which probably isn't
this namespace."
[_character]
nil)
(defn place-name
"Placeholder. We're going to have to have names of villages, towns, regions
and so on, and we're going to have to be able to retrieve those efficiently,
but I don't yet know how this is going to work. Definitely doesn't belong
in this namespace."
[_cell]
nil)
(defn match-on?
"Placeholder, utility function. Do all these `objects` have the same values for
these `keys`?"
[keys & objects]
(reduce = (map #(select-keys % keys) objects)))
(defprotocol ProtoCharacter
(full-name [character]
"Return the full name of this `character`, constructed according
to the default construction sequence")
(relative-name [character other]
"Return the name that `other` would naturally use in an
informal context to describe `character`")
(personal-name [character]
"Return the personal name of this `character`."))
(defrecord Character [object
agent
family-name
personal-name
occupation
rank
epithet
knowledge
wallet]
;; A character; obviously, normally, a non-player character, although the
;; player character is one of these. Essentially, an Agent which can speak,
;; which has knowledge, which has a set of affective relationships with other
;; characters.
;; ProtoContainer
ProtoAgent
ProtoCharacter
(personal-name [character] (:personal-name character))
(full-name [character]
(let [e (:epithet character)
h (honorific character)
f (:family-name character)
p (:personal-name character)
o (:occupation character)
l (place-name (:cell character))]
(cs/join " "
(remove nil?
(flatten
[e
h
f
p
(when o ["the" o])
(when l ["of" l])])))))
(relative-name [character other]
(let [e (:epithet character)
h (honorific character)
f (:family-name character)
p (:personal-name character)
o (:occupation character)
h (place-name (:cell character))
same-family? (= f (:family-name other))]
(cs/join " "
(remove nil?
(flatten
[(when-not (match-on?
[:family-name :cell] character other)
e)
(when-not same-family? h)
(when-not same-family? h)
p
(when (and o (not (match-on? :occupation))) ["the" o])
(when (and h
(not (match-on? [:cell] character other))) ["of" h])]))))))
(defn make-character
"Construct a Character record from this `seed` map"
[^IPersistentMap seed]
(let [object (make-object seed)
agent (make-actor seed)]
(apply Character.
(list (map seed [:agent
:family-name
:personal-name
:occupation
:rank
:epithet
:knowledge
:wallet])))))

View file

@ -0,0 +1 @@
(ns cc.journeyman.the-great-game.character.sex)

View file

@ -0,0 +1,87 @@
(ns cc.journeyman.the-great-game.launcher
"Launcher for the game"
(:require [clojure.tools.cli :refer [parse-opts]]
[jme-clj.core :refer [add-control add-to-root app-settings cam
defsimpleapp fly-cam get-height-map image
image-based-height-map load-height-map
load-texture material set* start
terrain-lod-control terrain-quad]])
(:import (com.jme3.texture Texture$WrapMode))
(:gen-class))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; Launcher: parses any command line options, and launches the game.
;;;;
;;;; This program is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License
;;;; as published by the Free Software Foundation; either version 2
;;;; of the License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
;;;; USA.
;;;;
;;;; Copyright (C) 2024 Simon Brooke
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def cli-options
;; An option with a required argument
[["-p" "--port PORT" "Port number"
:default 80
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
;; A non-idempotent option (:default is applied first)
["-v" nil "Verbosity level"
:id :verbosity
:default 0
:update-fn inc] ; Prior to 0.4.1, you would have to use:
;; :assoc-fn (fn [m k _] (update-in m [k] inc))
;; A boolean option defaulting to nil
["-h" "--help"]])
(defn init []
(set* (fly-cam) :move-speed 50)
(let [grass (set* (load-texture "textures/terrain/splat/grass.jpg") :wrap Texture$WrapMode/Repeat)
dirt (set* (load-texture "textures/terrain/splat/dirt.jpg") :wrap Texture$WrapMode/Repeat)
rock (set* (load-texture "textures/terrain/splat/road.jpg") :wrap Texture$WrapMode/Repeat)
mat (material "Common/MatDefs/Terrain/Terrain.j3md")
height-map-tex (load-texture "textures/terrain/splat/mountains512.png")
height-map (->> height-map-tex image image-based-height-map load-height-map)
patch-size 65
terrain (terrain-quad "my terrain" patch-size 513 (get-height-map height-map))]
(-> mat
(set* :texture "Alpha" (load-texture "textures/terrain/splat/alphamap.png"))
(set* :texture "Tex1" grass)
(set* :float "Tex1Scale" (float 64))
(set* :texture "Tex2" dirt)
(set* :float "Tex2Scale" (float 32))
(set* :texture "Tex3" rock)
(set* :float "Tex3Scale" (float 128)))
(-> terrain
(set* :material mat)
(set* :local-translation 0 -100 0)
(set* :local-scale 2 1 2)
(add-to-root)
(add-control (terrain-lod-control terrain (cam))))))
(defsimpleapp app :init init)
(defn -main
"Launch the game."
[& args]
(parse-opts args cli-options)
;; this isn't working, not sure why not.
;; (.setSettings app (app-settings false :dialog-image "images/splash.png"))
(start app))

View file

@ -26,7 +26,7 @@
microworld style world tagged with vegetation, etc, data). "
([height-map]
(let [drained-world (get-drainage-map height-map)]
(run-world drained-world (compile (slurp "resources/baking/biome-rules.txt")) (count drained-world))))
(run-world drained-world (compile (slurp "resources/data/baking/biome-rules.txt")) (count drained-world))))
([height-map _rainfall-map]
(get-biome-map height-map)))
@ -70,6 +70,7 @@
:camp (populate-npcs {:world world :cell cell :occupation :nomad})
:house (populate-npcs {:world world :cell cell :occupation :peasant})
:inn (populate-npcs {:world world :cell cell :occupation :innkeeper})
:market (populate-npcs {:world world :cell cell :occupation :merchant})
;; else
nil)]
(if npcs (assoc cell :npcs npcs)
@ -80,7 +81,7 @@
(probably some form of database) and return a structure which allows that
database o be interrogated."
[biome-map]
(let [world (run-world biome-map (compile (slurp "resources/baking/settlement-rules.txt")) (count biome-map))
(let [world (run-world biome-map (compile (slurp "resources/data/baking/settlement-rules.txt")) (count biome-map))
with-npcs (map-world world (vary-meta (fn [w c] (populate-cell w c)) assoc :rule-type :ad-hoc))]
;; right, with that settled world, I'm going to put one herdsman with
;; five animals (either all sheep, all cattle, all goats, all horses or
@ -101,7 +102,7 @@
:roadmap []}))
(defn get-road-map
[populated-world])
[_populated-world])
(defn prove
"Given this `height-map` (a monochrome raster) and optionally this

View file

@ -0,0 +1,40 @@
package java.cc.journeyman.the_great_game.game_objects;
import java.util.Collection;
import java.util.LinkedList;
/** Java implementation of a game object which is a container. */
public class JContainer extends JObject {
/** The contents of this container. */
private LinkedList<JObject> contents = new LinkedList<JObject>();
/** Get the contents of this container. */
public LinkedList<JObject> getContents() {
return this.contents;
}
/**
* Add this single item to this container.
*
* @param item
*/
public void addItem(JObject item) {
}
public void addItem(Object item) {
if (item instanceof JObject) {
this.addItem((JObject)item);
} else {
throw new IllegalArgumentException("All items added to a container must be game objects");
}
}
public void addItems(Collection<Object> items) {
for (Object item : items) {
this.contents.add((JObject) item);
}
}
}

View file

@ -0,0 +1,95 @@
package java.cc.journeyman.the_great_game.game_objects;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.scene.Spatial;
import com.jme3.scene.Node;
import java.lang.Math;
import java.security.SecureRandom;
/**
* Highly experimental! Using Java beans instead of Clojure records
* for underlying game objects, to allow type inheritance. It has the
* additional advantage that Java objects are inherently mutable. This is the
* root of the hierarchy, a basic object.
*
* See [JavaBeans Spec]
* (http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html).
*
* If this works(!), it is likely that object persistence and (re-loading) will
* happen in the Java layer rather than in the Clojure layer.
*
* A game object model in jMonkeyEngine is loaded as a 'Spatial' which
* is a subclass of 'Node', which has a 'BoundingVolume' i.v. which is
* instantiated as either a 'BoundingSphere' or a 'BoundingBox'. We
* can't directly extract length/width/height data from these... but we
* ought not to tolerate majow inconsistency between the size we report
* and the volume reported by the model.
*/
public class JObject {
private static SecureRandom idSeed = new SecureRandom();
/**
* When we have an actual database we'll probably get the id from
* the database...
*/
private long id = idSeed.nextLong();
private int weight = 0;
private int length = 0;
private int width = 0;
private int height = 0;
private Node model;
private BoundingVolume getBoundingVolume() {
BoundingVolume result = null;
// if (model instanceof Spatial) {
// result = (Spatial) model.getWorldBound();
// }
return result;
}
private double getMaxExtent() {
BoundingVolume v = this.getBoundingVolume();
double result = -1;
if (v instanceof BoundingBox) {
BoundingBox vb = (BoundingBox) v;
result = Math.max( Math.max( vb.getXExtent(), vb.getYExtent()),
vb.getZExtent());
} else if (v instanceof BoundingSphere ) {
result = 2 * ((BoundingSphere) v).getRadius();
}
return result;
}
/** */
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
/** Return the id of this instance, obviously. */
public long getId() {
return id;
}
}