nandi/jolt-nativepublic Fork 0
2271a9188bb358e15c020f96d9923f906e495c56
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

asteroids.clj · 153 lines · 6.1 KBClojure Blame HistoryRaw
Play three games on the gfx backend e98a184 nandi 11d ago1(ns glimmer-gfx.asteroids
2 "Asteroids: vector art, which is the one kind of graphics a line routine and
3 some trigonometry get you for free.
4
5 Every shape here is a list of points in model space, rotated and translated
6 each frame and drawn with `raster/poly!`. Nothing is a sprite, nothing is a
7 bitmap, and the whole world is a map of plain vectors -- so `step` is pure
8 and the loop is a `swap!` on it.
9
10 Left/right or A/D turn, W or up thrusts, space fires."
11 (:require [glimmer-gfx.raster :as r]
12 [glimmer-gfx.x11 :as w]))
13
14(def ^:private W 640)
15(def ^:private H 480)
16(def ^:private TURN 0.26) ; radians per frame
17(def ^:private COOLDOWN 4) ; frames between shots while space is held
18(def ^:private THRUST 0.18)
19(def ^:private DRAG 0.99)
20(def ^:private BULLET-SPEED 6.0)
21(def ^:private BULLET-LIFE 60) ; frames
22
23;; keysyms, as XLookupKeysym reports them
24(def ^:private LEFT #{0xff51 0x61}) (def ^:private RIGHT #{0xff53 0x64})
25(def ^:private THRUSTK #{0xff52 0x77}) (def ^:private FIRE #{0x20})
26
27;; --- geometry ---------------------------------------------------------------
28
29(defn wrap [[x y]] [(mod x W) (mod y H)])
30
31(defn- rotate [[x y] a]
32 (let [c (Math/cos a) s (Math/sin a)] [(- (* x c) (* y s)) (+ (* x s) (* y c))]))
33
34(defn- at [pts pos angle scale]
35 (mapv (fn [p] (mapv + pos (rotate (mapv * p [scale scale]) angle))) pts))
36
37(defn- dist2 [[ax ay] [bx by]]
38 (let [dx (- ax bx) dy (- ay by)] (+ (* dx dx) (* dy dy))))
39
40(defn hit?
41 "Circle overlap, on a wrapped world: compare against the nearest image of b."
42 [a b radius]
43 (let [[bx by] b]
44 (some #(< (dist2 a %) (* radius radius))
45 (for [ox [(- W) 0 W] oy [(- H) 0 H]] [(+ bx ox) (+ by oy)]))))
46
47(def ^:private ship-shape [[1 0] [-0.6 0.6] [-0.3 0] [-0.6 -0.6]])
48
49(defn- rock-shape
50 "A lumpy ring: eight points, each pushed out by a random amount."
51 []
52 (mapv (fn [i] (let [a (* i (/ (* 2 Math/PI) 8))
53 d (+ 0.7 (rand 0.5))]
54 [(* d (Math/cos a)) (* d (Math/sin a))]))
55 (range 8)))
56
57(defn- rock [size]
58 {:pos [(rand W) (rand H)]
59 :vel [(- (rand 2.4) 1.2) (- (rand 2.4) 1.2)]
60 :size size :shape (rock-shape)})
61
62(defn- rock-radius [size] (* 14 size))
63
64;; --- one frame --------------------------------------------------------------
65
66(defn- drift [o] (update o :pos #(wrap (mapv + % (:vel o)))))
67
68(defn- fly [ship keys]
69 (let [a (cond-> (:angle ship)
70 (some LEFT keys) (- TURN)
71 (some RIGHT keys) (+ TURN))
72 thrusting (boolean (some THRUSTK keys))
73 vel (cond-> (mapv #(* DRAG %) (:vel ship))
74 thrusting (->> (mapv + [(* THRUST (Math/cos a)) (* THRUST (Math/sin a))])))]
75 (assoc (drift (assoc ship :angle a :vel vel)) :thrusting thrusting)))
76
77(defn- split
78 "A rock that was shot becomes two smaller ones, or nothing at size 1."
79 [{:keys [pos size]}]
80 (when (> size 1)
81 (repeatedly 2 #(assoc (rock (dec size)) :pos pos))))
82
83(defn- collide
84 "Bullets against rocks: whatever each hit, gone; each rock hit, split."
85 [bullets rocks]
86 (let [struck (into {} (for [b bullets
87 rk rocks
88 :when (hit? (:pos b) (:pos rk) (rock-radius (:size rk)))]
89 [rk b]))]
90 [(remove (set (vals struck)) bullets)
91 (into (vec (remove struck rocks)) (mapcat split (keys struck)))
92 (count struck)]))
93
94(defn step
95 "One frame. Pure: state and the keys held in, state out."
96 [{:keys [ship bullets rocks score fired?] :as st} keys]
97 (let [ship (fly ship keys)
98 firing? (boolean (some FIRE keys))
99 ;; hold to auto-fire, but not every frame: COOLDOWN paces it
100 cool (if firing? (dec (or (:cool st) 0)) 0)
101 shoot? (and firing? (not (pos? cool)))
102 bullets (cond->> (->> bullets
103 (map #(update (drift %) :life dec))
104 (filter #(pos? (:life %))))
105 shoot?
106 (cons {:pos (:pos ship) :life BULLET-LIFE
107 :vel [(* BULLET-SPEED (Math/cos (:angle ship)))
108 (* BULLET-SPEED (Math/sin (:angle ship)))]}))
109 [bullets rocks n] (collide bullets (map drift rocks))
110 dead? (some #(hit? (:pos ship) (:pos %) (rock-radius (:size %))) rocks)]
111 (assoc st :ship ship :bullets (vec bullets) :fired? firing?
112 :cool (if shoot? COOLDOWN cool)
113 :rocks (if (seq rocks) (vec rocks) (vec (repeatedly 5 #(rock 3))))
114 :score (+ score (* 10 n))
115 :lost? (boolean dead?))))
116
117;; --- paint ------------------------------------------------------------------
118
119(def ^:private colours {:bg 0x0b0d12 :ship 0xffffff :flame 0xd08050
120 :rock 0x9aa0b0 :shot 0x5a6ea0 :fg 0xffffff})
121
122(defn- draw! [buf {:keys [ship bullets rocks score lost?]}]
123 (r/clear buf (:bg colours))
124 (doseq [rk rocks]
125 (r/poly! buf (at (:shape rk) (:pos rk) 0 (rock-radius (:size rk))) (:rock colours)))
126 (doseq [b bullets]
127 (let [[x y] (:pos b)] (r/rect! buf (int x) (int y) 2 2 (:shot colours))))
128 (when-not lost?
129 (r/poly! buf (at ship-shape (:pos ship) (:angle ship) 10) (:ship colours))
130 (when (:thrusting ship)
131 (r/poly! buf (at [[-0.6 0.35] [-1.3 0] [-0.6 -0.35]] (:pos ship) (:angle ship) 10)
132 (:flame colours))))
133 (r/text! buf 8 8 (str "SCORE " score) (:fg colours) 2)
134 (when lost?
135 (r/text! buf (- (quot W 2) 60) (quot H 2) "GAME OVER" (:fg colours) 3)))
136
137(defn new-game []
138 {:ship {:pos [(/ W 2.0) (/ H 2.0)] :vel [0 0] :angle 0}
139 :bullets [] :rocks (vec (repeatedly 5 #(rock 3))) :score 0 :fired? false :cool 0})
140
141(defn -main [& _]
142 (let [state (atom (new-game))]
143 (w/run-window
144 {:width W :height H :title "asteroids"}
145 (fn [buf input]
146 (draw! buf (swap! state
147 (fn [st]
148 ;; dead: space starts a new game rather than firing
149 (if (:lost? st)
150 (if (and (some FIRE (:keys input)) (not (:fired? st)))
151 (new-game)
152 (assoc st :fired? (boolean (some FIRE (:keys input)))))
153 (step st (:keys input))))))))))