orca/breakout Orca

Code

main.odin ¶
491 linesSource

1package src
2
3/*
4Original Source: https://github.com/orca-app/orca/blob/main/samples/breakout/src/main.c
5
6Can be run using in the local folder
71. odin.exe build main.odin -file -target:orca_wasm32 -out:module.wasm 
82. orca bundle --name orca_output --resource-dir data module.wasm
93. orca_output\bin\orca_output.exe
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 // 7 * 6
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// This is upside down from how it will actually be drawn.
32velocity := oc.vec2{5, 5}
33
34//odinfmt: disable
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// @(export)
143// oc_on_terminate :: proc "c" () {
144// 	if score == NUM_BLOCKS_TO_WIN {
145// 		oc.log_info("you win!\n")
146// 	} else {
147// 		oc.log_info("goodbye world!\n")
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	// oc.log_info("key down: %i", key)
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	// oc.log_info("key up: %i", key)
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// Returns a cardinal direction 1-8 for the collision with the block, or zero
200// if no collision. 1 is straight up and directions proceed clockwise.
201check_collision :: proc "contextless" (block: oc.rect) -> int {
202	// Note that all the logic for this game has the origin in the bottom left.
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		// Ball is fully outside block
210		return 0
211	}
212
213	// If moving right, the ball can bounce off its top right corner, right
214	// side, or bottom right corner. Corner bounces occur if the block's bottom
215	// left corner is in the ball's top right quadrant, or if the block's top
216	// left corner is in the ball's bottom left quadrant. Otherwise, an edge
217	// bounce occurs if the block's left edge falls in either of the ball's
218	// right quadrants.
219	//
220	// This logic generalizes to other directions.
221	//
222	// We assume significant tunneling can't happen.
223
224	ballCenter := oc.vec2{ball.x + ball.w / 2, ball.y + ball.h / 2}
225
226	// Moving right
227	if velocity.x > 0 {
228		// Ball's top right corner
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		// Ball's bottom right corner
237		if ballCenter.x <= block.x &&
238		   block.x <= ballx2 &&
239		   ball.y <= blocky2 &&
240		   blocky2 <= ballCenter.y {
241			return 4
242		}
243
244		// Ball's right edge
245		if ballCenter.x <= block.x && block.x <= ballx2 {
246			return 3
247		}
248	}
249
250	// Moving up
251	if velocity.y > 0 {
252		// Ball's top left corner
253		if ball.x <= blockx2 &&
254		   blockx2 <= ballCenter.x &&
255		   ballCenter.y <= block.y &&
256		   block.y <= bally2 {
257			return 8
258		}
259
260		// Ball's top right corner
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		// Ball's top edge
269		if ballCenter.y <= block.y && block.y <= bally2 {
270			return 1
271		}
272	}
273
274	// Moving left
275	if velocity.x < 0 {
276		// Ball's bottom left corner
277		if ball.x <= blockx2 &&
278		   blockx2 <= ballCenter.x &&
279		   ball.y <= blocky2 &&
280		   blocky2 <= ballCenter.y {
281			return 6
282		}
283
284		// Ball's top left corner
285		if ball.x <= blockx2 &&
286		   blockx2 <= ballCenter.x &&
287		   ballCenter.y <= block.y &&
288		   block.y <= bally2 {
289			return 8
290		}
291
292		// Ball's left edge
293		if ball.x <= blockx2 && blockx2 <= ballCenter.x {
294			return 7
295		}
296	}
297
298	// Moving down
299	if velocity.y < 0 {
300		// Ball's bottom right corner
301		if ballCenter.x <= block.x &&
302		   block.x <= ballx2 &&
303		   ball.y <= blocky2 &&
304		   blocky2 <= ballCenter.y {
305			return 4
306		}
307
308		// Ball's bottom left corner
309		if ball.x <= blockx2 &&
310		   blockx2 <= ballCenter.x &&
311		   ball.y <= blocky2 &&
312		   blocky2 <= ballCenter.y {
313			return 6
314		}
315
316		// Ball's bottom edge
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, //NOTE: we render with y-up so we need to flip bounding box coordinates.
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		// draw score text
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}

Other Files

data/test_read.txt ¶
1 lineSource

Hello, ressource file

Assets 4

Declarations Used 59