1use macroquad::prelude::*;
2
3const SHIP_HEIGHT: f32 = 25.;
4const SHIP_BASE: f32 = 22.;
5struct Ship {
6 pos: Vec2,
7 rot: f32,
8 vel: Vec2,
9}
10
11struct Bullet {
12 pos: Vec2,
13 vel: Vec2,
14 shot_at: f64,
15 collided: bool,
16}
17
18struct Asteroid {
19 pos: Vec2,
20 vel: Vec2,
21 rot: f32,
22 rot_speed: f32,
23 size: f32,
24 sides: u8,
25 collided: bool,
26}
27
28fn wrap_around(v: &Vec2) -> Vec2 {
29 let mut vr = Vec2::new(v.x, v.y);
30 if vr.x > screen_width() {
31 vr.x = 0.;
32 }
33 if vr.x < 0. {
34 vr.x = screen_width()
35 }
36 if vr.y > screen_height() {
37 vr.y = 0.;
38 }
39 if vr.y < 0. {
40 vr.y = screen_height()
41 }
42 vr
43}
44
45#[macroquad::main("Asteroids")]
46async fn main() {
47 let mut ship = Ship {
48 pos: Vec2::new(screen_width() / 2., screen_height() / 2.),
49 rot: 0.,
50 vel: Vec2::new(0., 0.),
51 };
52
53 let mut bullets = Vec::new();
54 let mut last_shot = get_time();
55 let mut asteroids = Vec::new();
56 let mut gameover = false;
57
58 let mut screen_center;
59
60 loop {
61 if gameover {
62 clear_background(LIGHTGRAY);
63 let mut text = "You Win!. Press [enter] to play again.";
64 let font_size = 30.;
65
66 if asteroids.len() > 0 {
67 text = "Game Over. Press [enter] to play again.";
68 }
69 let text_size = measure_text(text, None, font_size as _, 1.0);
70 draw_text(
71 text,
72 screen_width() / 2. - text_size.width / 2.,
73 screen_height() / 2. - text_size.height / 2.,
74 font_size,
75 DARKGRAY,
76 );
77 if is_key_down(KeyCode::Enter) {
78 ship = Ship {
79 pos: Vec2::new(screen_width() / 2., screen_height() / 2.),
80 rot: 0.,
81 vel: Vec2::new(0., 0.),
82 };
83 bullets = Vec::new();
84 asteroids = Vec::new();
85 gameover = false;
86 screen_center = Vec2::new(screen_width() / 2., screen_height() / 2.);
87 for _ in 0..10 {
88 asteroids.push(Asteroid {
89 pos: screen_center
90 + Vec2::new(rand::gen_range(-1., 1.), rand::gen_range(-1., 1.))
91 .normalize()
92 * screen_width().min(screen_height())
93 / 2.,
94 vel: Vec2::new(rand::gen_range(-1., 1.), rand::gen_range(-1., 1.)),
95 rot: 0.,
96 rot_speed: rand::gen_range(-2., 2.),
97 size: screen_width().min(screen_height()) / 10.,
98 sides: rand::gen_range(3, 8),
99 collided: false,
100 })
101 }
102 }
103 next_frame().await;
104 continue;
105 }
106 let frame_t = get_time();
107 let rotation = ship.rot.to_radians();
108
109 let mut acc = -ship.vel / 100.; if is_key_down(KeyCode::Up) {
113 acc = Vec2::new(rotation.sin(), -rotation.cos()) / 3.;
114 }
115
116 if is_key_down(KeyCode::Space) && frame_t - last_shot > 0.5 {
118 let rot_vec = Vec2::new(rotation.sin(), -rotation.cos());
119 bullets.push(Bullet {
120 pos: ship.pos + rot_vec * SHIP_HEIGHT / 2.,
121 vel: rot_vec * 7.,
122 shot_at: frame_t,
123 collided: false,
124 });
125 last_shot = frame_t;
126 }
127
128 if is_key_down(KeyCode::Right) {
130 ship.rot += 5.;
131 } else if is_key_down(KeyCode::Left) {
132 ship.rot -= 5.;
133 }
134
135 ship.vel += acc;
137 if ship.vel.length() > 5. {
138 ship.vel = ship.vel.normalize() * 5.;
139 }
140 ship.pos += ship.vel;
141 ship.pos = wrap_around(&ship.pos);
142
143 for bullet in bullets.iter_mut() {
145 bullet.pos += bullet.vel;
146 }
147
148 for asteroid in asteroids.iter_mut() {
150 asteroid.pos += asteroid.vel;
151 asteroid.pos = wrap_around(&asteroid.pos);
152 asteroid.rot += asteroid.rot_speed;
153 }
154
155 bullets.retain(|bullet| bullet.shot_at + 1.5 > frame_t);
157
158 let mut new_asteroids = Vec::new();
159 for asteroid in asteroids.iter_mut() {
160 if (asteroid.pos - ship.pos).length() < asteroid.size + SHIP_HEIGHT / 3. {
162 gameover = true;
163 break;
164 }
165
166 for bullet in bullets.iter_mut() {
168 if (asteroid.pos - bullet.pos).length() < asteroid.size {
169 asteroid.collided = true;
170 bullet.collided = true;
171
172 if asteroid.sides > 3 {
174 new_asteroids.push(Asteroid {
175 pos: asteroid.pos,
176 vel: Vec2::new(bullet.vel.y, -bullet.vel.x).normalize()
177 * rand::gen_range(1., 3.),
178 rot: rand::gen_range(0., 360.),
179 rot_speed: rand::gen_range(-2., 2.),
180 size: asteroid.size * 0.8,
181 sides: asteroid.sides - 1,
182 collided: false,
183 });
184 new_asteroids.push(Asteroid {
185 pos: asteroid.pos,
186 vel: Vec2::new(-bullet.vel.y, bullet.vel.x).normalize()
187 * rand::gen_range(1., 3.),
188 rot: rand::gen_range(0., 360.),
189 rot_speed: rand::gen_range(-2., 2.),
190 size: asteroid.size * 0.8,
191 sides: asteroid.sides - 1,
192 collided: false,
193 })
194 }
195 break;
196 }
197 }
198 }
199
200 bullets.retain(|bullet| bullet.shot_at + 1.5 > frame_t && !bullet.collided);
202 asteroids.retain(|asteroid| !asteroid.collided);
203 asteroids.append(&mut new_asteroids);
204
205 if asteroids.len() == 0 {
207 gameover = true;
208 }
209
210 if gameover {
211 continue;
212 }
213
214 clear_background(LIGHTGRAY);
215
216 for bullet in bullets.iter() {
217 draw_circle(bullet.pos.x, bullet.pos.y, 2., BLACK);
218 }
219
220 for asteroid in asteroids.iter() {
221 draw_poly_lines(
222 asteroid.pos.x,
223 asteroid.pos.y,
224 asteroid.sides,
225 asteroid.size,
226 asteroid.rot,
227 2.,
228 BLACK,
229 )
230 }
231
232 let v1 = Vec2::new(
233 ship.pos.x + rotation.sin() * SHIP_HEIGHT / 2.,
234 ship.pos.y - rotation.cos() * SHIP_HEIGHT / 2.,
235 );
236 let v2 = Vec2::new(
237 ship.pos.x - rotation.cos() * SHIP_BASE / 2. - rotation.sin() * SHIP_HEIGHT / 2.,
238 ship.pos.y - rotation.sin() * SHIP_BASE / 2. + rotation.cos() * SHIP_HEIGHT / 2.,
239 );
240 let v3 = Vec2::new(
241 ship.pos.x + rotation.cos() * SHIP_BASE / 2. - rotation.sin() * SHIP_HEIGHT / 2.,
242 ship.pos.y + rotation.sin() * SHIP_BASE / 2. + rotation.cos() * SHIP_HEIGHT / 2.,
243 );
244 draw_triangle_lines(v1, v2, v3, 2., BLACK);
245
246 next_frame().await
247 }
248}