main.odin ¶
1package src
2
3
4
5
6
7
8
9
10
11
12import "base:runtime"
13import "core:log"
14import "core:fmt"
15import "core:math"
16import oc "core:sys/orca"
17
18ctx: runtime.Context
19
20NUM_BLOCKS_PER_ROW :: 7
21NUM_BLOCKS :: 42
22NUM_BLOCKS_TO_WIN :: NUM_BLOCKS - 2
23BLOCKS_WIDTH :: f32(810.0)
24BLOCK_HEIGHT :: f32(30.0)
25BLOCKS_PADDING :: f32(15.0)
26BLOCKS_BOTTOM :: f32(300.0)
27PADDLE_MAX_LAUNCH_ANGLE :: 0.7
28
29BLOCK_WIDTH :: (BLOCKS_WIDTH - ((NUM_BLOCKS_PER_ROW + 1) * BLOCKS_PADDING)) / NUM_BLOCKS_PER_ROW
30
31
32velocity := oc.vec2{5, 5}
33
34
35blockHealth := [NUM_BLOCKS]int {
36 0,
37 1,
38 1,
39 1,
40 1,
41 1,
42 0,
43 1,
44 1,
45 1,
46 1,
47 1,
48 1,
49 1,
50 2,
51 2,
52 2,
53 2,
54 2,
55 2,
56 2,
57 2,
58 2,
59 2,
60 2,
61 2,
62 2,
63 2,
64 3,
65 3,
66 3,
67 3,
68 3,
69 3,
70 3,
71 3,
72 3,
73 3,
74 3,
75 3,
76 3,
77 3,
78}
79score := 0
80
81leftDown := false
82rightDown := false
83
84frameSize := oc.vec2{100, 100}
85
86surface: oc.surface
87renderer: oc.canvas_renderer
88canvas: oc.canvas_context
89
90waterImage: oc.image
91brickImage: oc.image
92ballImage: oc.image
93font: oc.font
94
95paddleColor := oc.color{{1, 0, 0, 1}, .RGB}
96paddle := oc.rect{300, 50, 200, 24}
97ball := oc.rect{200, 200, 20, 20}
98
99flip_y :: proc "contextless" (r: oc.rect) -> oc.mat2x3 {
100 return {1, 0, 0, 0, -1, 2 * r.y + r.w}
101}
102
103flip_y_at :: proc "contextless" (pos: oc.vec2) -> oc.mat2x3 {
104 return {1, 0, 0, 0, -1, 2 * pos.y}
105}
106
107main :: proc() {
108 context.logger = oc.create_odin_logger()
109 ctx = context
110
111 oc.window_set_title("Breakout")
112
113 renderer = oc.canvas_renderer_create()
114 surface = oc.canvas_surface_create(renderer)
115 canvas = oc.canvas_context_create()
116
117 waterImage = oc.image_create_from_path(renderer, "/underwater.jpg", false)
118 brickImage = oc.image_create_from_path(renderer, "/brick.png", false)
119 ballImage = oc.image_create_from_path(renderer, "/ball.png", false)
120
121 if oc.image_is_nil(waterImage) {
122 oc.log_error("couldn't load water image\n")
123 }
124 if oc.image_is_nil(brickImage) {
125 oc.log_error("couldn't load brick image\n")
126 }
127 if oc.image_is_nil(ballImage) {
128 oc.log_error("couldn't load ball image\n")
129 }
130
131 ranges := [5]oc.unicode_range {
132 oc.UNICODE_BASIC_LATIN,
133 oc.UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT,
134 oc.UNICODE_LATIN_EXTENDED_A,
135 oc.UNICODE_LATIN_EXTENDED_B,
136 oc.UNICODE_SPECIALS,
137 }
138
139 font = oc.font_create_from_path("/Literata-SemiBoldItalic.ttf", 5, &ranges[0])
140}
141
142
143
144
145
146
147
148
149
150
151@(export)
152oc_on_resize :: proc "c" (width, height: u32) {
153 context = ctx
154 log.infof("frame resize %v, %v", width, height)
155 frameSize.x = f32(width)
156 frameSize.y = f32(height)
157}
158
159@(export)
160oc_on_key_down :: proc "c" (scan: oc.scan_code, key: oc.key_code) {
161
162 oc.log_ext(.INFO, "keydown", "breakout.odin", 112, "key up: %i", key)
163
164 if key == .LEFT {
165 leftDown = true
166 }
167
168 if key == .RIGHT {
169 rightDown = true
170 }
171}
172
173@(export)
174oc_on_key_up :: proc "c" (scan: oc.scan_code, key: oc.key_code) {
175
176 oc.log_ext(.INFO, "keyup", "breakout.odin", 126, "key up: %i", key)
177
178 if key == .LEFT {
179 leftDown = false
180 }
181
182 if key == .RIGHT {
183 rightDown = false
184 }
185}
186
187block_rect :: proc "contextless" (i: int) -> oc.rect {
188 row := f32(i / NUM_BLOCKS_PER_ROW)
189 col := f32(i % NUM_BLOCKS_PER_ROW)
190
191 return {
192 BLOCKS_PADDING + (BLOCKS_PADDING + BLOCK_WIDTH) * col,
193 BLOCKS_BOTTOM + (BLOCKS_PADDING + BLOCK_HEIGHT) * row,
194 BLOCK_WIDTH,
195 BLOCK_HEIGHT,
196 }
197}
198
199
200
201check_collision :: proc "contextless" (block: oc.rect) -> int {
202
203 ballx2 := ball.x + ball.w
204 bally2 := ball.y + ball.h
205 blockx2 := block.x + block.w
206 blocky2 := block.y + block.h
207
208 if ballx2 < block.x || blockx2 < ball.x || bally2 < block.y || blocky2 < ball.y {
209
210 return 0
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 ballCenter := oc.vec2{ball.x + ball.w / 2, ball.y + ball.h / 2}
225
226
227 if velocity.x > 0 {
228
229 if ballCenter.x <= block.x &&
230 block.x <= ballx2 &&
231 ballCenter.y <= block.y &&
232 block.y <= bally2 {
233 return 2
234 }
235
236
237 if ballCenter.x <= block.x &&
238 block.x <= ballx2 &&
239 ball.y <= blocky2 &&
240 blocky2 <= ballCenter.y {
241 return 4
242 }
243
244
245 if ballCenter.x <= block.x && block.x <= ballx2 {
246 return 3
247 }
248 }
249
250
251 if velocity.y > 0 {
252
253 if ball.x <= blockx2 &&
254 blockx2 <= ballCenter.x &&
255 ballCenter.y <= block.y &&
256 block.y <= bally2 {
257 return 8
258 }
259
260
261 if ballCenter.x <= block.x &&
262 block.x <= ballx2 &&
263 ballCenter.y <= block.y &&
264 block.y <= bally2 {
265 return 2
266 }
267
268
269 if ballCenter.y <= block.y && block.y <= bally2 {
270 return 1
271 }
272 }
273
274
275 if velocity.x < 0 {
276
277 if ball.x <= blockx2 &&
278 blockx2 <= ballCenter.x &&
279 ball.y <= blocky2 &&
280 blocky2 <= ballCenter.y {
281 return 6
282 }
283
284
285 if ball.x <= blockx2 &&
286 blockx2 <= ballCenter.x &&
287 ballCenter.y <= block.y &&
288 block.y <= bally2 {
289 return 8
290 }
291
292
293 if ball.x <= blockx2 && blockx2 <= ballCenter.x {
294 return 7
295 }
296 }
297
298
299 if velocity.y < 0 {
300
301 if ballCenter.x <= block.x &&
302 block.x <= ballx2 &&
303 ball.y <= blocky2 &&
304 blocky2 <= ballCenter.y {
305 return 4
306 }
307
308
309 if ball.x <= blockx2 &&
310 blockx2 <= ballCenter.x &&
311 ball.y <= blocky2 &&
312 blocky2 <= ballCenter.y {
313 return 6
314 }
315
316
317 if ball.y <= blocky2 && blocky2 <= ballCenter.y {
318 return 5
319 }
320 }
321
322 return 0
323}
324
325@(export)
326oc_on_frame_refresh :: proc "c" () {
327 context = ctx
328
329 scratch := oc.scratch_begin()
330 defer oc.scratch_end(scratch)
331
332 if leftDown {
333 paddle.x -= 10
334 } else if rightDown {
335 paddle.x += 10
336 }
337 paddle.x = clamp(paddle.x, 0, frameSize.x - paddle.w)
338
339 ball.x += velocity.x
340 ball.y += velocity.y
341 ball.x = clamp(ball.x, 0, frameSize.x - ball.w)
342 ball.y = clamp(ball.y, 0, frameSize.y - ball.h)
343
344 if ball.x + ball.w >= frameSize.x {
345 velocity.x = -velocity.x
346 }
347 if ball.x <= 0 {
348 velocity.x = -velocity.x
349 }
350 if ball.y + ball.h >= frameSize.y {
351 velocity.y = -velocity.y
352 }
353
354 if ball.y <= paddle.y + paddle.h &&
355 ball.x + ball.w >= paddle.x &&
356 ball.x <= paddle.x + paddle.w &&
357 velocity.y < 0 {
358 t := ((ball.x + ball.w / 2) - paddle.x) / paddle.w
359 launchAngle := math.lerp(f32(-PADDLE_MAX_LAUNCH_ANGLE), f32(PADDLE_MAX_LAUNCH_ANGLE), t)
360 speed := math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y)
361 velocity = (oc.vec2){math.sin(launchAngle) * speed, math.cos(launchAngle) * speed}
362 ball.y = paddle.y + paddle.h
363
364 oc.log_info("PONG!")
365 }
366
367 if ball.y <= 0 {
368 ball.x = frameSize.x / 2. - ball.w
369 ball.y = frameSize.y / 2. - ball.h
370 }
371
372 for i in 0 ..< NUM_BLOCKS {
373 if blockHealth[i] <= 0 {
374 continue
375 }
376
377 r := block_rect(i)
378 result := check_collision(r)
379 if result != 0 {
380 log.infof("Collision! direction=%v", result)
381 blockHealth[i] -= 1
382
383 if blockHealth[i] == 0 {
384 score += 1
385 }
386
387 vx := velocity.x
388 vy := velocity.y
389
390 switch (result) {
391 case 1, 5:
392 velocity.y = -vy
393 case 3, 7:
394 velocity.x = -vx
395 case 2, 6:
396 velocity.x = -vy
397 velocity.y = -vx
398 case 4, 8:
399 velocity.x = vy
400 velocity.y = vx
401 }
402 }
403 }
404
405 if score == NUM_BLOCKS_TO_WIN {
406 oc.request_quit()
407 }
408
409 oc.canvas_context_select(canvas)
410
411 oc.set_color_rgba(10.0 / 255.0, 31.0 / 255.0, 72.0 / 255.0, 1)
412 oc.clear()
413
414 oc.image_draw(waterImage, (oc.rect){0, 0, frameSize.x, frameSize.y})
415
416 yUp := oc.mat2x3{1, 0, 0, 0, -1, frameSize.y}
417
418 oc.matrix_multiply_push(yUp)
419 {
420 for i in 0 ..< NUM_BLOCKS {
421 if blockHealth[i] <= 0 {
422 continue
423 }
424
425 r := block_rect(i)
426
427 oc.set_image(brickImage)
428 oc.set_color_rgba(0.9, 0.9, 0.9, 1)
429 oc.rounded_rectangle_fill(r.x, r.y, r.w, r.h, 4)
430 oc.set_image(oc.image_nil())
431
432 oc.set_color_rgba(0.6, 0.6, 0.6, 1)
433 oc.set_width(2)
434 oc.rounded_rectangle_stroke(r.x, r.y, r.w, r.h, 4)
435
436 fontSize := 18
437 text := fmt.tprintf("%d", blockHealth[i])
438 textRect := oc.font_text_metrics(font, f32(fontSize), text).ink
439
440 textPos := oc.vec2 {
441 r.x + r.w / 2 - textRect.w / 2 - textRect.x,
442 r.y + r.h / 2 - textRect.h / 2 - textRect.y - textRect.h,
443 }
444
445 oc.set_color_rgba(0.9, 0.9, 0.9, 1)
446 oc.circle_fill(r.x + r.w / 2, r.y + r.h / 2, r.h / 2.5)
447
448 oc.set_color_rgba(0, 0, 0, 1)
449 oc.set_font(font)
450 oc.set_font_size(18)
451 oc.move_to(textPos.x, textPos.y)
452 oc.matrix_multiply_push(flip_y_at(textPos))
453 {
454 oc.text_outlines(text)
455 oc.fill()
456 }
457 oc.matrix_pop()
458 }
459
460 oc.set_color(paddleColor)
461 oc.rounded_rectangle_fill(paddle.x, paddle.y, paddle.w, paddle.h, 4)
462
463 oc.matrix_multiply_push(flip_y(ball))
464 {
465 oc.image_draw(ballImage, ball)
466 }
467 oc.matrix_pop()
468
469
470 {
471 oc.move_to(20, 20)
472 text := fmt.tprintf(
473 "Destroy all %d blocks to win! Current score: %d",
474 NUM_BLOCKS_TO_WIN,
475 score,
476 )
477 textPos := oc.vec2{20, 20}
478 oc.matrix_multiply_push(flip_y_at(textPos))
479 {
480 oc.set_color_rgba(0.9, 0.9, 0.9, 1)
481 oc.text_outlines(text)
482 oc.fill()
483 }
484 oc.matrix_pop()
485 }
486 }
487 oc.matrix_pop()
488
489 oc.canvas_render(renderer, canvas, surface)
490 oc.canvas_present(renderer, surface)
491}