Compare commits

...

3 Commits

Author SHA1 Message Date
c14bcecccc Wind first pass 2023-11-26 13:15:24 -06:00
572726df7c Restructure, add laf 2023-11-26 12:22:56 -06:00
7342f6dbc8 Subscribe to game loop, UI now does (yoda I am) 2023-11-21 17:02:09 -06:00
5 changed files with 85 additions and 52 deletions

View File

@@ -4,7 +4,8 @@
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"} :url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.11.1"] :dependencies [[org.clojure/clojure "1.11.1"]
[seesaw "1.5.0"]] [seesaw "1.5.0"]
[com.formdev/flatlaf "3.2.5"]]
:main ^:skip-aot age-of-sail.core :main ^:skip-aot age-of-sail.core
:target-path "target/%s" :target-path "target/%s"
:profiles {:uberjar {:aot :all :profiles {:uberjar {:aot :all

View File

@@ -8,12 +8,10 @@
(def revenge (ref {:position [0. 0.] (def revenge (ref {:position [0. 0.]
:name "Revenge" :name "Revenge"
:heading (normalize [1.0 1.0]) :heading (normalize [0. 1.0])
:slots [{:type :downwind-sail :length 2 :furl 1.0}] :slots [{:type :downwind-sail :length 2 :furl 1.0}]
:velocity [1. 0.]})) :velocity [1. 0.]}))
(reset! ships [virginia-woolfe revenge]) (reset! ships [virginia-woolfe revenge])
(show-ui) (show-ui)
@tracked-ship

View File

@@ -1,11 +1,22 @@
(ns age-of-sail.core (ns age-of-sail.core
(:require [age-of-sail.vec2 :refer :all] (:require [age-of-sail.simulation :refer :all]
[age-of-sail.vec2 :refer :all]
[seesaw.core :refer :all] [seesaw.core :refer :all]
[seesaw.bind :as b] [seesaw.bind :as b])
[clojure.math :as math])) (:import com.formdev.flatlaf.FlatDarculaLaf))
;; Wind
(def tickrate 100) (def hardcoded-winds '({:strength [0.1 3.0] :position [0. 0.]})) ;; A strong easternly wind!
(def hardcoded-wind [0.1 3.0]) ;; A strong easternly wind! (defn sum-wind
"Wind at x is the sum of each wind force divided by its distance away"
[x winds]
(reduce (fn [sum wind]
(let [dist (len (sub x (:position wind)))]
(if (> dist 1.0)
(add sum (scale (:strength wind) (/ (len (sub x (:position wind))))))
(add sum (:strength wind)))))
(zero)
winds))
;; Ships
(defonce ships (atom [])) (defonce ships (atom []))
(defn ship-names (defn ship-names
@@ -35,67 +46,53 @@
force))) force)))
(defn physics-step (defn physics-step
[ship wind] [ship winds]
(dosync (dosync
;; Update position (let [wind (sum-wind (:position @ship) winds)]
(alter ship update :position add (scale (:velocity @ship) (/ tickrate))) ;; Update position
;; linear dampening (alter ship update :position add (scale (:velocity @ship) (/ tickrate)))
(alter ship update :velocity scale (- 1.0 (/ 0.5 tickrate))) ;; linear dampening
;; wind force (alter ship update :velocity scale (- 1.0 (/ 0.5 tickrate)))
(alter ship update :velocity add (scale (downwind-sails-force @ship wind) (/ tickrate))))) ;; wind force
(alter ship update :velocity add (scale (downwind-sails-force @ship wind) (/ tickrate))))))
(defn tick (defn tick
[ships] [ships]
(doseq [ship ships] (doseq [ship ships]
(-> ship (physics-step hardcoded-wind)))) (-> ship (physics-step hardcoded-winds))))
;; Simulation controls (subscribe! #(tick @ships))
(defonce program (atom :stopped))
(defn game-loop
[]
(while (#{:running :paused} @program)
(when (= :running @program)
(tick @ships))
(Thread/sleep (quot 1000 tickrate)))
(when-not (compare-and-set! program :killed :stopped)
(throw "Error: tried to stop a program that wasn't killed!")))
(defn pause-program
[]
(compare-and-set! program :running :paused))
(defn kill-program
[]
(compare-and-set! program :running :paused)
(compare-and-set! program :paused :killed))
(defn start-program
[]
(when (= (first (reset-vals! program :running)) :stopped)
(.start (Thread. game-loop))))
;; UI ;; UI
(FlatDarculaLaf/setup)
(defn ignore-args (defn ignore-args
[f] [f]
(fn [& _] (f))) (fn [& _] (f)))
(def tracked-ship (atom nil)) (def tracked-ship (atom nil))
(def ship-pos (b/notify-later))
(defn update-pos
[]
(let [ship @tracked-ship]
(when ship
(b/notify ship-pos (:position @ship)))))
(subscribe! update-pos)
(defn simulation-controls (defn simulation-controls
[] []
(let [start-button (button :text "Start" :listen [:mouse-clicked (ignore-args start-program)]) (let [start-button (button :text "Start" :listen [:mouse-clicked (ignore-args start-program)])
pause-button (button :text "Pause" :listen [:mouse-clicked (ignore-args pause-program)]) pause-button (button :text "Pause" :listen [:mouse-clicked (ignore-args pause-program)])
kill-button (button :text "Kill" :listen [:mouse-clicked (ignore-args kill-program)])] kill-button (button :text "Kill" :listen [:mouse-clicked (ignore-args kill-program)])]
(letfn [(start-enabled? [state] (contains? #{:stopped :paused} state)) (letfn [(start-enabled? [state] (contains? #{:stopped :paused} state))
(pause-enabled? [state] (= :running state)) (pause-enabled? [state] (= :running state))
(kill-enabled? [state] (contains? #{:running :paused} state))] (kill-enabled? [state] (contains? #{:running :paused} state))]
(b/bind program (b/tee (b/bind program (b/tee
(b/bind (b/transform start-enabled?) (b/property start-button :enabled?)) (b/bind (b/transform start-enabled?) (b/property start-button :enabled?))
(b/bind (b/transform pause-enabled?) (b/property pause-button :enabled?)) (b/bind (b/transform pause-enabled?) (b/property pause-button :enabled?))
(b/bind (b/transform kill-enabled?) (b/property kill-button :enabled?)))) (b/bind (b/transform kill-enabled?) (b/property kill-button :enabled?))))
(config! start-button :enabled? (start-enabled? @program)) (config! start-button :enabled? (start-enabled? @program))
(config! pause-button :enabled? (pause-enabled? @program)) (config! pause-button :enabled? (pause-enabled? @program))
(config! kill-button :enabled? (kill-enabled? @program)) (config! kill-button :enabled? (kill-enabled? @program))
(horizontal-panel :items [start-button pause-button kill-button])))) (horizontal-panel :items [start-button pause-button kill-button]))))
(defn ship-chooser (defn ship-chooser
@@ -106,16 +103,14 @@
(flow-panel :items ["Ship Name" name]))) (flow-panel :items ["Ship Name" name])))
(defn format-position (defn format-position
[ship] [[x y]]
(apply format "X: %.2f Y: %.2f" (:position @ship))) (format "X: %.2f Y: %.2f" x y))
(defn ship-info (defn ship-info
[] []
(let [panel (vertical-panel :visible? false) (let [panel (vertical-panel :visible? false)
position (label)] position (label)]
(b/bind tracked-ship (b/some identity) (b/bind ship-pos (b/transform format-position) (b/property position :text))
(b/tee
(b/bind (b/transform format-position) (b/value position))))
(add! panel (flow-panel :items ["Position" position])) (add! panel (flow-panel :items ["Position" position]))
panel)) panel))
@@ -134,6 +129,7 @@
show!))) show!)))
(defn -main (defn -main
[args] []
(start-program) (start-program)
(show-ui)) (show-ui))

View File

@@ -0,0 +1,34 @@
(ns age-of-sail.simulation)
(def tickrate 100)
(def hooks (atom '()))
(defn subscribe!
"Adds a hook to the hooks"
[f]
(swap! hooks conj f))
(defonce program (atom :stopped))
(defn game-loop
[]
(while (#{:running :paused} @program)
(when (= :running @program)
(doseq [hook @hooks]
(hook)))
(Thread/sleep (quot 1000 tickrate)))
(when-not (compare-and-set! program :killed :stopped)
(throw "Error: tried to stop a program that wasn't killed!")))
(defn pause-program
[]
(compare-and-set! program :running :paused))
(defn kill-program
[]
(compare-and-set! program :running :paused)
(compare-and-set! program :paused :killed))
(defn start-program
[]
(when (= (first (reset-vals! program :running)) :stopped)
(.start (Thread. game-loop))))

View File

@@ -9,6 +9,10 @@
[v1 v2] [v1 v2]
(mapv + v1 v2)) (mapv + v1 v2))
(defn sub
[v1 v2]
(mapv - v1 v2))
(defn scale (defn scale
[v & scalars] [v & scalars]
(mapv #(* (apply * scalars) %) v)) (mapv #(* (apply * scalars) %) v))