aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples/09-ghoulspree.lisp
blob: a11e6a6700355d8c02d2ddd2ef4b12d44a8f88af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
;;;; examples/09-ghoulspree.lisp

(defpackage #:ww.examples/9
  (:use #:cl)
  (:export #:start)
  (:import-from #:defclass-std #:defclass/std))

(in-package #:ww.examples/9)

;;; CLASSES

(defclass/std  ghoulspree (ww::application)
  ((ghouls-per-click :std 20)
   (collision-on-p :std t)
   (gravity-on-p :std nil)))

(defclass/std ghoul (ww:image)
  ((dx dy dr :std)))

;;; UTILITY FUNCTIONS

(defun make-ghoul (x y)
  (make-instance 'ghoul :texture (ww:get-asset "Fezghoul.png")
                        :x x :y y
                        :dr (random-velocity 0.2)
                        :dx (random-velocity 4)
                        :dy (random-velocity 4)))

(defun out-of-bounds-p (ghoul)
  (not
   (and
    (< -50 (ww:x ghoul) 850)
    (< -50 (ww:y ghoul) 650))))

(defun random-velocity (&optional (size 1.0))
  (* size (if (zerop (random 2))
              (random 1.0)
              (* -1 (random 1.0)))))

(defun advance-pos (thing)
  (with-accessors ((dr dr) (dx dx) (dy dy) (x ww::x) (y ww::y) (r ww::rotation)) thing
    (incf x dx)
    (incf y dy)
    (incf r dr))) 

(defun clamp (lo val hi)
  "Returns VAL if (< LO VAL HI), otherwise returns LO or HI depending
on which boundary VAL is outside of."
  (max lo (min val hi)))

(defun sign (x)
  (if (zerop x) 0
      (/ x (abs x))))

(defmacro with-pairs ((a b) vector &rest body)
  (alexandria:with-gensyms (idxa idxb vec)
    `(loop
       with ,vec = ,vector
       for ,idxa from 0 to (- (length ,vec) 2)
       for ,a = (aref ,vec ,idxa) do
         (loop for ,idxb from (1+ ,idxa) to (1- (length ,vec))
               for ,b = (aref ,vec ,idxb) do
                 (progn ,@body)))))

(defun handle-collision (g1 g2 &optional (friction 0.99))
  (with-slots ((dx1 dx) (dy1 dy) (dr1 dr)) g1
    (with-slots ((dx2 dx) (dy2 dy) (dr2 dr)) g2
      (let ((tdx (* friction dx1))
            (tdy (* friction dy1))
            (tdr (* friction dr1)))
        (setf dx1 (* friction dx2)
              dy1 (* friction dy2)
              dr1 (* friction dr2)
              dx2 tdx
              dy2 tdy
              dr2 tdr)))))

(defun apply-gravity-to (thing acc)
  (with-slots (dx dy) thing
    (decf dy acc)))

(ww:defhandler moveghouls
    (ww:on-perframe (app)
      ;; first handle collisions
      (when (collision-on-p app)
        (with-pairs
            (g1 g2) (ww::application-scene app)
            (when (ww:units-intersect-p g1 g2)
              (handle-collision g1 g2 1.0)
              ;; need a "bounce" 
              (advance-pos g1)
              (advance-pos g1)
              (advance-pos g2)
              (advance-pos g2))))
      ;; then update positions and remove the out of bounds
      (let ((gravity
              (gravity-on-p app))
            (accelleration
              (/ 9.8 (ww:fps app)))) 
        (loop for ghoul across (ww::application-scene app)
              do (advance-pos ghoul)
              when gravity
                do (apply-gravity-to ghoul accelleration)
              when (out-of-bounds-p ghoul)
                ;; note, it is generally a BadIdea™ to delete
                ;; something from an array that you are iterating
                ;; over. It only works as expected here b/c of
                ;; implicit knowledge about the imlementation of the
                ;; scene container. A safer way in most cases would be
                ;; to `collect ghoul into drop-list` and then deleted
                ;; all the collected ghouls in a `finally` clause.
                do (ww:drop-unit ghoul))))) 

(defun random-sign ()
  (if (zerop (random 2)) -1 1))

(defun random-between (lo hi)
  (+ lo (random (- hi lo))))

(ww:defhandler add-ghouls
    (ww:on-mousedown (app x y)
      (loop repeat (ghouls-per-click app)
            for rx = (random 800); (+ x (* (random-sign) (random-between 30 60)))
            for ry = (random 600); (+ y (* (random-sign) (random-between 30 60)))
            do (ww:add-unit (make-ghoul rx ry)))
      (format t "~a ghouls on screen~%"
              (length (ww::application-scene app )))))


(ww:defhandler toggle-collision
    (ww:on-keydown (app scancode)
      (case scancode
        (:scancode-c 
         (format t "collision: ~a~%"
                 (setf (collision-on-p app)
                       (not (collision-on-p app)))))
        (:scancode-g
         (format t "gravity: ~a~%"
                 (setf (gravity-on-p app)
                       (not (gravity-on-p app))))))))


(defmethod ww::boot ((app ghoulspree))
  "Adds the intro text and sets up the start button handler."
  (format t "Click to add ~a ghouls to the screen.~%" (ghouls-per-click app))
  (format t "Press c to toggle collision handling.~%")
  (format t "Press g to toggle gravity.~%")

  (ww:add-handler app #'add-ghouls)
  (ww:add-handler app #'moveghouls)
  (ww:add-handler app #'toggle-collision))

(defun start (&optional (scale 1.0))
  (ww::start
   (make-instance
    'ghoulspree
    :fps 60
    :width (round (* 800 scale))
    :height (round (* 600 scale))
    :scale scale
    :refocus-on-mousedown-p nil
    :title "Click to add ghouls"
    :asset-root
    (merge-pathnames
     "examples/"
     (asdf:system-source-directory :wheelwork)))))