1
1
import bullet
2
2
import dungeon
3
3
import gleam/bool
4
+ import gleam/int
4
5
import gleam/list
5
- import p5js_gleam . { type P5 }
6
+ import p5js_gleam . { type Assets , type P5 }
6
7
import p5js_gleam/bindings as p5
7
8
import player
8
9
import utils
9
10
import vector
10
11
11
12
/// Represents the overall game state
12
13
type WorldState {
13
- /// State of the world when the game is active
14
+ /// The start screen of the game.
15
+ StartScreen
16
+ /// State of the world when the game is active.
14
17
GameRunning (
15
18
dungeon : dungeon . Dungeon ,
16
19
player : player . Player ,
17
20
bullets : List ( bullet . Bullet ) ,
21
+ score : Int ,
18
22
)
19
- GameOver ( fell_in_pitch : Bool )
20
- // enemies: List(Enemy),
23
+ /// The game over screen when the player has lost.
24
+ GameOver ( fell_in_pit : Bool , score : Int )
25
+ }
26
+
27
+ fn preload ( p : P5 ) -> Assets {
28
+ let minecraftia = p5 . load_font ( p , "./fonts/Minecraftia-Regular.ttf" )
29
+ let worksans = p5 . load_font ( p , "./fonts/WorkSans-Medium.ttf" )
30
+
31
+ p5js_gleam . initialize_assets ( )
32
+ |> p5js_gleam . insert_font ( "minecraftia" , minecraftia )
33
+ |> p5js_gleam . insert_font ( "worksans" , worksans )
21
34
}
22
35
23
36
fn setup ( p : P5 ) -> WorldState {
24
37
let canvas_size = dungeon . total_size ( )
25
38
p5 . create_canvas ( p , canvas_size , canvas_size )
26
- let dungeon = dungeon . generate_dungeon ( )
27
- GameRunning (
28
- dungeon ,
29
- player . new_player ( vector . Vector ( canvas_size /. 2.0 , canvas_size /. 2.0 , 0.0 ) ) ,
30
- [ ] ,
31
- )
39
+ StartScreen
32
40
}
33
41
34
- fn draw ( p : P5 , state : WorldState ) {
42
+ fn draw ( p : P5 , state : WorldState , assets : Assets ) {
43
+ let center_text = fn ( txt : String ) {
44
+ let canvas_size = dungeon . total_size ( )
45
+ canvas_size /. 2.0 -. p5 . text_width ( p , txt ) /. 2.0
46
+ }
47
+ let h1 = 40
48
+ let h2 = 30
49
+ let h3 = 22
50
+ let h4 = 18
51
+ let par = 14
52
+ let assert Ok ( minecraftia ) = p5js_gleam . get_font ( assets , "minecraftia" )
53
+ let assert Ok ( worksans ) = p5js_gleam . get_font ( assets , "worksans" )
54
+ let canvas_size = dungeon . total_size ( )
55
+
35
56
p5 . background ( p , "#000000" )
36
57
case state {
37
- GameRunning ( dungeon , player , bullets ) -> {
58
+ StartScreen -> {
59
+ let title = "BULLET HECK"
60
+ let start = "Press 'SPACE' to start"
61
+ let ctrl = "Controls"
62
+ let ctrl_move = "Use arrow keys or WASD to move"
63
+ let ctrl_jump = "Press SPACE to jump"
64
+ let ctrl_shoot = "Use mouse to aim, click to shoot"
65
+
66
+ p
67
+ |> p5 . fill ( "#0f0f0f" )
68
+ |> p5 . rect ( 0.0 , 0.0 , canvas_size , canvas_size )
69
+ |> p5 . text_size ( h1 )
70
+ |> p5 . fill ( "#ffffff" )
71
+ |> p5 . text_font ( minecraftia )
72
+ |> p5 . text ( title , center_text ( title ) , canvas_size /. 2.0 +. 5.0 )
73
+ |> p5 . text_font ( worksans )
74
+ |> p5 . text_size ( h4 )
75
+ |> p5 . text ( start , center_text ( start ) , canvas_size /. 2.0 +. 20.0 )
76
+ // controls
77
+ |> p5 . fill ( "#969696" )
78
+ |> p5 . text ( ctrl , center_text ( ctrl ) , canvas_size /. 2.0 +. 200.0 )
79
+ |> p5 . text_size ( par )
80
+ |> p5 . text ( ctrl_move , center_text ( ctrl_move ) , canvas_size /. 2.0 +. 225.0 )
81
+ |> p5 . text ( ctrl_jump , center_text ( ctrl_jump ) , canvas_size /. 2.0 +. 245.0 )
82
+ |> p5 . text (
83
+ ctrl_shoot ,
84
+ center_text ( ctrl_shoot ) ,
85
+ canvas_size /. 2.0 +. 265.0 ,
86
+ )
87
+ }
88
+ GameRunning ( dungeon , player , bullets , score ) -> {
38
89
dungeon . draw ( p , dungeon )
39
90
player . draw ( p , player )
40
91
list . each ( bullets , bullet . draw ( p , _) )
92
+
93
+ // Render UI
94
+ let hp_x = 22.0
95
+ let score_x = canvas_size -. 100.0
96
+
41
97
p
98
+ |> p5 . no_stroke ( )
99
+ |> p5 . fill ( "#660000" )
100
+ |> p5 . rect ( 0.0 , 0.0 , canvas_size , 36.0 )
101
+ // draw health bar
102
+ |> p5 . fill ( "#28e200" )
103
+ |> p5 . rect ( hp_x +. 15.0 , 12.0 , int . to_float ( player . current_health ) , 10.0 )
104
+ |> p5 . fill ( "#ffffff" )
105
+ |> p5 . text_size ( par )
106
+ |> p5 . text ( "HP:" , 10.0 , hp_x )
107
+ // draw score
108
+ |> p5 . text ( "Score:" , score_x , 22.0 )
109
+ |> p5 . text ( int . to_string ( score ) , score_x +. 45.0 , 22.0 )
42
110
}
43
- GameOver ( _ ) -> {
111
+ GameOver ( fell_in_pit , score ) -> {
112
+ let canvas_size = dungeon . total_size ( )
113
+ let game_over = "GAME OVER"
114
+ let final_score = "Final Score: "
115
+ let restart = "Press 'R' to play again"
116
+ let score_text = final_score <> int . to_string ( score )
117
+ let cause_of_death = case fell_in_pit {
118
+ True -> "You fell to your death."
119
+ False -> "Turns out, red things hurt."
120
+ }
121
+
44
122
p
123
+ |> p5 . fill ( "#0f0f0f" )
124
+ |> p5 . rect ( 0.0 , 0.0 , canvas_size , canvas_size )
125
+ |> p5 . fill ( "#e20000" )
126
+ |> p5 . text_size ( h2 )
127
+ |> p5 . text_font ( minecraftia )
128
+ |> p5 . text ( game_over , center_text ( game_over ) , canvas_size /. 2.0 -. 25.0 )
129
+ |> p5 . text_size ( h3 )
130
+ |> p5 . text_font ( worksans )
131
+ |> p5 . text (
132
+ cause_of_death ,
133
+ center_text ( cause_of_death ) ,
134
+ canvas_size /. 2.0 -. 15.0 ,
135
+ )
136
+ |> p5 . fill ( "#ffffff" )
137
+ |> p5 . text (
138
+ score_text ,
139
+ center_text ( score_text ) ,
140
+ canvas_size /. 2.0 +. 45.0 ,
141
+ )
142
+ |> p5 . text_size ( h4 )
143
+ |> p5 . text ( restart , center_text ( restart ) , canvas_size /. 2.0 +. 70.0 )
45
144
}
46
145
}
47
146
}
48
147
49
148
fn on_key_pressed ( key : String , _ : Int , state : WorldState ) -> WorldState {
50
149
case key , state {
51
- " " , GameRunning ( dungeon , player , bullets ) ->
150
+ "r" , _ -> StartScreen
151
+ " " , StartScreen -> {
152
+ let canvas_size = dungeon . total_size ( )
153
+ let dungeon = dungeon . generate_dungeon ( )
154
+ GameRunning (
155
+ dungeon ,
156
+ player . new_player ( vector . Vector (
157
+ canvas_size /. 2.0 ,
158
+ canvas_size /. 2.0 ,
159
+ 0.0 ,
160
+ ) ) ,
161
+ [ ] ,
162
+ 0 ,
163
+ )
164
+ }
165
+ " " , GameRunning ( dungeon , player , bullets , score ) ->
52
166
GameRunning (
53
167
dungeon : dungeon ,
54
168
bullets : bullets ,
55
169
player : player . jump ( player ) ,
170
+ score : score ,
56
171
)
57
- "w" , GameRunning ( dungeon , player , bullets ) ->
172
+ "w" , GameRunning ( dungeon , player , bullets , score ) ->
58
173
GameRunning (
59
174
dungeon : dungeon ,
60
175
bullets : bullets ,
61
176
player : player . accelerate_y ( player , False ) ,
177
+ score : score ,
62
178
)
63
- "s" , GameRunning ( dungeon , player , bullets ) ->
179
+ "s" , GameRunning ( dungeon , player , bullets , score ) ->
64
180
GameRunning (
65
181
dungeon : dungeon ,
66
182
bullets : bullets ,
67
183
player : player . accelerate_y ( player , True ) ,
184
+ score : score ,
68
185
)
69
- "a" , GameRunning ( dungeon , player , bullets ) ->
186
+ "a" , GameRunning ( dungeon , player , bullets , score ) ->
70
187
GameRunning (
71
188
dungeon : dungeon ,
72
189
bullets : bullets ,
73
190
player : player . accelerate_x ( player , False ) ,
191
+ score : score ,
74
192
)
75
- "d" , GameRunning ( dungeon , player , bullets ) ->
193
+ "d" , GameRunning ( dungeon , player , bullets , score ) ->
76
194
GameRunning (
77
195
dungeon : dungeon ,
78
196
bullets : bullets ,
79
197
player : player . accelerate_x ( player , True ) ,
198
+ score : score ,
80
199
)
81
200
_ , _ -> state
82
201
}
83
202
}
84
203
85
204
fn on_key_released ( key : String , _ : Int , state : WorldState ) -> WorldState {
86
205
case key , state {
87
- "w" , GameRunning ( dungeon , player , bullets ) ->
206
+ "w" , GameRunning ( dungeon , player , bullets , score ) ->
88
207
GameRunning (
89
208
dungeon : dungeon ,
90
209
bullets : bullets ,
91
210
player : player . stop_y ( player ) ,
211
+ score : score ,
92
212
)
93
- "s" , GameRunning ( dungeon , player , bullets ) ->
213
+ "s" , GameRunning ( dungeon , player , bullets , score ) ->
94
214
GameRunning (
95
215
dungeon : dungeon ,
96
216
bullets : bullets ,
97
217
player : player . stop_y ( player ) ,
218
+ score : score ,
98
219
)
99
- "a" , GameRunning ( dungeon , player , bullets ) ->
220
+ "a" , GameRunning ( dungeon , player , bullets , score ) ->
100
221
GameRunning (
101
222
dungeon : dungeon ,
102
223
bullets : bullets ,
103
224
player : player . stop_x ( player ) ,
225
+ score : score ,
104
226
)
105
- "d" , GameRunning ( dungeon , player , bullets ) ->
227
+ "d" , GameRunning ( dungeon , player , bullets , score ) ->
106
228
GameRunning (
107
229
dungeon : dungeon ,
108
230
bullets : bullets ,
109
231
player : player . stop_x ( player ) ,
232
+ score : score ,
110
233
)
111
234
_ , _ -> state
112
235
}
113
236
}
114
237
115
238
fn on_mouse_clicked ( x : Float , y : Float , state : WorldState ) -> WorldState {
116
239
case state {
117
- GameRunning ( dungeon , player , bullets ) -> {
240
+ GameRunning ( dungeon , player , bullets , score ) -> {
118
241
use <- bool . guard ( ! player . can_player_fire ( player ) , state )
119
242
120
243
let firing_direction =
@@ -137,6 +260,7 @@ fn on_mouse_clicked(x: Float, y: Float, state: WorldState) -> WorldState {
137
260
.. player ,
138
261
last_fire_time : utils . now_in_milliseconds ( ) ,
139
262
) ,
263
+ score : score ,
140
264
)
141
265
}
142
266
_ -> state
@@ -145,7 +269,7 @@ fn on_mouse_clicked(x: Float, y: Float, state: WorldState) -> WorldState {
145
269
146
270
fn on_tick ( state : WorldState ) -> WorldState {
147
271
case state {
148
- GameRunning ( dungeon , player , bullets ) -> {
272
+ GameRunning ( dungeon , player , bullets , score ) -> {
149
273
// Attempt to move player
150
274
let old_position = player . position
151
275
let moved = player . move ( player )
@@ -166,9 +290,9 @@ fn on_tick(state: WorldState) -> WorldState {
166
290
}
167
291
168
292
use <- bool . guard (
169
- player . position . z <. 0.0
293
+ player . position . z <= . 0.0
170
294
&& dungeon . is_over_pit ( dungeon , player . position ) ,
171
- GameOver ( True ) ,
295
+ GameOver ( True , score ) ,
172
296
)
173
297
174
298
let player = player . update_velocity ( player )
@@ -203,14 +327,23 @@ fn on_tick(state: WorldState) -> WorldState {
203
327
# ( [ bullet . advance_bullet ( b ) , .. bullets ] , player )
204
328
} )
205
329
206
- GameRunning ( dungeon : dungeon , player : player , bullets : bullets )
330
+ GameRunning (
331
+ dungeon : dungeon ,
332
+ player : player ,
333
+ bullets : bullets ,
334
+ score : score ,
335
+ )
207
336
}
208
337
_ -> state
209
338
}
210
339
}
211
340
212
341
pub fn main ( ) {
213
- p5js_gleam . create_sketch ( init : setup , draw : draw )
342
+ p5js_gleam . create_sketch_with_preloading (
343
+ init : setup ,
344
+ draw : draw ,
345
+ preload : preload ,
346
+ )
214
347
|> p5js_gleam . set_on_key_pressed ( on_key_pressed )
215
348
|> p5js_gleam . set_on_key_released ( on_key_released )
216
349
|> p5js_gleam . set_on_mouse_clicked ( on_mouse_clicked )
0 commit comments