This repository has been archived by the owner on Aug 5, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
game-state.lisp
201 lines (180 loc) · 8.85 KB
/
game-state.lisp
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
(require :sdl2)
(require :sdl2-image)
(setf *random-state* (make-random-state t))
(defparameter *time-to-start* 10)
(defparameter *default-lives* 3)
(defclass game-state ()
((map :initarg :map
:reader game-map
:initform (error "no value for slot 'map'")
:documentation "The game map object")
(spritemap :initarg :spritemap
:initform (error "no value for slot 'spritemap'")
:documentation "SDL2 surface that contains the spritemap")
(player :initarg :player
:reader game-player
:initform (error "no value for slot 'player'"))
(ghosts :reader game-ghosts)
(level :initform 1)
(score :accessor game-score
:initform 0)
(dots :accessor game-dots
:initform 0)
(lives :initform *default-lives* :documentation "Remaining lives of the player")
(timer-start :documentation "Timestamp of the game start")
(time-at-pause :documentation "Game time spent paused (in seconds)")
(stage :initform 'init))
(:documentation "Representation of the game state"))
(defmethod initialize-instance :after ((game game-state) &rest rest)
"Initialize the player's game-state field."
(declare (ignore rest))
(with-slots (game-state) (game-player game)
(setf game-state game)))
(defun get-random-strategy (owner &optional (max-index 4))
"Return a randomly chosen tracking strategy for the ghost."
(let ((class (ecase (random max-index)
(0 'track-follow)
(1 'track-ambush)
(2 'track-patrol)
(3 'track-random))))
(make-instance class :owner owner)))
(defmethod generate-ghosts ((game game-state))
"Generate different ghosts based on the current level and map properties."
(with-slots (ghosts level map) game
(setf ghosts (list))
(with-slots (ghost-spawn-gate ghost-spawns max-ghosts) map
(let* ((position (copy-list ghost-spawn-gate))
(new-ghost (make-instance 'ghost :index 0 :position position :game-state game)))
(setf (ghost-strategy new-ghost) (make-instance 'track-follow :owner new-ghost))
(push new-ghost ghosts))
(loop for i from 1
for spawn-place in ghost-spawns
for position = (copy-list spawn-place)
for new-ghost = (make-instance 'ghost :index i :position position :game-state game)
do (setf (ghost-strategy new-ghost) (get-random-strategy new-ghost))
(push new-ghost ghosts)))))
(defmethod reset-game ((game game-state))
"Reset the game state (level, score, dots and lives)."
(with-slots (map level score dots lives) game
(setf level 1)
(setf score 0)
(setf dots (fill-with-dots map))
(setf lives *default-lives*)))
(defmethod init-game ((game game-state))
"Start the game: generate ghosts, set player position and next stage."
(with-slots (player ghosts stage map) game
(with-slots (position) player ;; reset player state
(setf position (copy-list (player-spawn map))))
(generate-ghosts game)
(setf stage 'start)))
(defmethod handle-collision ((game game-state))
"Handle a collision between a player and a ghost."
(with-slots (lives stage) game
(decf lives)
(if (= lives 0)
(setf stage 'defeat)
(progn (setf stage 'init)
;; (draw-dialog-with-timeout
;; (format nil "You got caught, but you still have ~A ~A left!" lives
;; (if (= lives 1) "life" "lives")))
))))
(defmethod simulate-entities ((game game-state))
"Simulate a single game tick - move entities forward, check collisions and check the win condition."
(with-slots (dots ghosts level map player stage) game
(loop for g in (game-ghosts game)
do (move-and-check-collision g))
(move-and-check-collision player)
(when (= dots 0)
(incf level)
(setf dots (fill-with-dots map))
(setf stage 'init)
;; (draw-dialog-with-timeout (format nil "Congratulations! You're not at level ~A!" level))
)))
(defmethod game-duration ((game game-state))
"Compute the total game duration."
(with-slots (time-at-pause timer-start) game
(+ (- (get-universal-time) timer-start) time-at-pause)))
(defmethod draw-entities ((game game-state) renderer)
"Draw the entities with the given renderer."
(draw (game-player game) renderer)
(loop for g in (game-ghosts game)
do (draw g renderer)))
(defmethod draw-hud ((game game-state) renderer)
"Draw the HUD with the given renderer."
;; (format t "Drawing HUD to the screen~%")
)
(defun draw-dialog (renderer text)
"Draw a dialog with the given renderer."
;; (format t "Drawing ~A to the screen~%" text)
)
(defun draw-dialog-with-timeout (window renderer text)
"Draw a dialog with the given renderer, blocking the app for some time."
())
(defmethod game-loop ((game game-state))
"The main game loop."
(with-slots (map player spritemap stage time-at-pause timer-start) game
(reset-game game)
(recompute-draw-props +default-window-width+ +default-window-height+ map)
(sdl2:with-init (:everything)
(sdl2:with-window (window :title "puck-man"
:flags '(:input-focus :resizable :shown)
:w +default-window-width+ :h +default-window-height+)
(sdl2:with-renderer (renderer window :flags '(:accelerated :targettexture))
(setf *spritemap-texture* (sdl2:create-texture-from-surface renderer spritemap))
(sdl2:with-event-loop (:method :poll)
(:windowevent (:event event :data1 width :data2 height)
(when (= event sdl2-ffi:+sdl-windowevent-resized+) ;; Window resized
(recompute-draw-props width height map)))
(:keyup (:keysym keysym)
(let ((keycode (sdl2:scancode-value keysym)))
(if (sdl2:scancode= keycode :scancode-escape)
(sdl2:push-event :quit))
(case stage
(start (setf time-at-pause 0)
(setf timer-start (get-universal-time))
(setf stage 'countdown))
(countdown (when (sdl2:scancode= keycode :scancode-p)
(setf stage 'paused))
(set-next-dir player keycode))
(playing (when (sdl2:scancode= keycode :scancode-p)
(setf stage 'paused)
(incf time-at-pause (- (get-universal-time) timer-start)))
(set-next-dir player keycode))
(paused (if (sdl2:scancode= keycode :scancode-p)
(setf stage 'playing)))
(defeat (when (sdl2:scancode= keycode :scancode-r)
(reset-game game)
(setf stage 'init))))))
(:idle ()
(sdl2:set-render-draw-color renderer 0 0 0 255)
(sdl2:render-clear renderer)
(draw (game-map game) renderer)
(ecase stage
(init (init-game game))
(start (draw-entities game renderer)
(draw-dialog renderer "Press any key to start the game."))
(countdown (draw-entities game renderer)
(draw-hud game renderer)
(when (> (- (get-universal-time) timer-start) *time-to-start*)
(setf time-at-pause 0)
(setf timer-start (get-universal-time))
(setf stage 'playing)))
(playing (handler-case (simulate-entities game)
(entity-collision () (handle-collision game)))
(draw-entities game renderer)
(draw-hud game renderer))
(paused (draw-dialog renderer "Game paused. Press 'P' to unpause."))
(defeat (draw-dialog renderer "Game over! Press 'R' to restart.")))
(sdl2:render-present renderer)
(sdl2:delay *frame-delay*))
(:quit () t)))))))
(defparameter *default-map* (with-open-file (input "resources/default.map") (make-game-map input)))
(defparameter *default-spritemap* (sdl2-image:load-png-rw "resources/spritemap.png"))
(defun game-main ()
"Entry point for the game."
(let* ((player (make-instance 'player :position (copy-list (player-spawn *default-map*))))
(game-state (make-instance 'game-state :map *default-map* :spritemap *default-spritemap* :player player)))
(game-loop game-state)))
;; (push '*default-pathname-defaults* asdf:*central-registry*)
;; (asdf:load-system :puck-man)
;; (game-main)