raylib/tetroid

Code

raylib_tetroid.odin ¶
603 linesSource

1package main
2
3
4/*******************************************************************************************
5*
6*   raylib - classic game: tetroid
7*
8*   Sample game developed by Marc Palau and Ramon Santamaria
9*
10*   This game has been created using raylib v1.3 (www.raylib.com)
11*   raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
12*
13*  Translation from https://github.com/raysan5/raylib-games/blob/master/classics/src/tetris.c to Odin
14*
15*   Copyright (c) 2015 Ramon Santamaria (@raysan5)
16*   Copyright (c) 2021 Ginger Bill
17*
18********************************************************************************************/
19
20
21
22import rl "vendor:raylib"
23
24SQUARE_SIZE             :: 20
25
26GRID_HORIZONTAL_SIZE    :: 12
27GRID_VERTICAL_SIZE      :: 20
28
29LATERAL_SPEED           :: 10
30TURNING_SPEED           :: 12
31FAST_FALL_AWAIT_COUNTER :: 30
32
33FADING_TIME             :: 33
34
35SCREEN_WIDTH  :: 800
36SCREEN_HEIGHT :: 450
37
38Grid_Square :: enum u8 {
39	Empty,
40	Moving,
41	Full,
42	Block,
43	Fading,
44}
45
46game_over := false
47pause := false
48
49grid:           [GRID_HORIZONTAL_SIZE][GRID_VERTICAL_SIZE]Grid_Square
50piece:          [4][4]Grid_Square
51incoming_piece: [4][4]Grid_Square
52
53piece_position: [2]i32
54
55fading_color: rl.Color
56
57begin_play := true
58piece_active := false
59detection := false
60line_to_delete := false
61
62level := 1
63lines := 0
64
65gravity_movement_counter := 0
66lateral_movement_counter := 0
67turn_movement_counter := 0
68fast_fall_movement_counter := 0
69
70fade_line_counter := 0
71
72inverse_gravity_speed := 30
73
74
75
76main :: proc() {
77	rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Tetroid")
78	defer rl.CloseWindow()   
79
80	init_game()
81
82	rl.SetTargetFPS(60)      
83
84	for !rl.WindowShouldClose() { // Detect window close button or ESC key
85		update_game()
86		draw_game()
87	}
88}
89
90
91init_game :: proc() {
92	level = 1
93	lines = 0
94
95	fading_color = rl.GRAY
96
97	piece_position = {0, 0}
98
99	pause = false
100
101	begin_play     = true
102	piece_active   = false
103	detection      = false
104	line_to_delete = false
105
106	// Counters
107	gravity_movement_counter = 0
108	lateral_movement_counter = 0
109	turn_movement_counter = 0
110	fast_fall_movement_counter = 0
111
112	fade_line_counter = 0
113	inverse_gravity_speed = 30
114
115	grid = {}
116	incoming_piece = {}
117
118	// Initialize grid matrices
119	for i in 0..<GRID_HORIZONTAL_SIZE {
120		for j in 0..<GRID_VERTICAL_SIZE {
121			switch {
122			case j == GRID_VERTICAL_SIZE - 1,
123				i == GRID_HORIZONTAL_SIZE - 1,
124				i == 0:
125				grid[i][j] = .Block
126			}
127		}
128	}
129}
130
131update_game :: proc() {
132	if game_over {
133		if rl.IsKeyPressed(.ENTER) {
134			init_game()
135			game_over = false
136		}
137		return
138	}
139
140	if rl.IsKeyPressed(.P) {
141		pause = !pause
142	}
143
144	if pause {
145		return
146	}
147
148	if line_to_delete {
149		fade_line_counter += 1
150
151		if fade_line_counter % 8 < 4 {
152			fading_color = rl.MAROON
153		} else {
154			fading_color = rl.GRAY
155		}
156
157		if fade_line_counter >= FADING_TIME {
158			delete_complete_lines()
159			fade_line_counter = 0
160			line_to_delete = false
161
162			lines += 1
163		}
164		return
165	}
166
167
168	if !piece_active {
169		piece_active = create_piece()
170		fast_fall_movement_counter = 0
171	} else {
172		fast_fall_movement_counter += 1
173		gravity_movement_counter   += 1
174		lateral_movement_counter   += 1
175		turn_movement_counter      += 1
176
177		// We make sure to move if we've pressed the key this frame
178		if rl.IsKeyPressed(.LEFT) || rl.IsKeyPressed(.RIGHT) {
179			lateral_movement_counter = LATERAL_SPEED
180		}
181		if rl.IsKeyPressed(.UP) {
182			turn_movement_counter = TURNING_SPEED
183		}
184
185		// Fall down
186		if rl.IsKeyDown(.DOWN) && fast_fall_movement_counter >= FAST_FALL_AWAIT_COUNTER {
187			// We make sure the piece is going to fall this frame
188			gravity_movement_counter += inverse_gravity_speed
189		}
190
191		if gravity_movement_counter >= inverse_gravity_speed {
192			// Basic falling movement
193			check_detection(&detection)
194
195			// Check if the piece has collided with another piece or with the boundings
196			resolve_falling_movement(&detection, &piece_active)
197
198			// Check if we fullfilled a line and if so, erase the line and pull down the the lines above
199			check_completion(&line_to_delete)
200
201			gravity_movement_counter = 0
202		}
203
204		// Move laterally at player's will
205		if lateral_movement_counter >= LATERAL_SPEED {
206			// Update the lateral movement and if success, reset the lateral counter
207			if !resolve_lateral_movement() {
208				lateral_movement_counter = 0
209			}
210		}
211
212		// Turn the piece at player's will
213		if turn_movement_counter >= TURNING_SPEED {
214			// Update the turning movement and reset the turning counter
215			if resolve_turn_movement() {
216				turn_movement_counter = 0
217			}
218		}
219
220		for j in 0..<2 {
221			for i in 1..<GRID_HORIZONTAL_SIZE-1 {
222				if grid[i][j] == .Full {
223					game_over = true
224				}
225			}
226		}
227	}
228}
229
230draw_game :: proc() {
231	rl.BeginDrawing()
232	defer rl.EndDrawing()
233
234	rl.ClearBackground(rl.RAYWHITE)
235
236	if game_over {
237		text :: "PRESS [ENTER] TO PLAY AGAIN"
238		rl.DrawText(text, rl.GetScreenWidth()/2 - rl.MeasureText(text, 20)/2, rl.GetScreenHeight()/2 - 50, 20, rl.GRAY)
239		return
240	}
241
242	offset := [2]i32{
243		SCREEN_WIDTH/2 - (GRID_HORIZONTAL_SIZE*SQUARE_SIZE/2) - 50,
244		SCREEN_HEIGHT/2 - ((GRID_VERTICAL_SIZE-1)*SQUARE_SIZE/2) + SQUARE_SIZE*2,
245	}
246
247	offset.y -= 50
248
249	controller := offset.x
250
251	for j in 0..<GRID_VERTICAL_SIZE {
252		for i in 0..<GRID_HORIZONTAL_SIZE {
253			switch grid[i][j] {
254			case .Empty:
255				rl.DrawLine(offset.x, offset.y, offset.x + SQUARE_SIZE, offset.y, rl.LIGHTGRAY)
256				rl.DrawLine(offset.x, offset.y, offset.x, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
257				rl.DrawLine(offset.x + SQUARE_SIZE, offset.y, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
258				rl.DrawLine(offset.x, offset.y + SQUARE_SIZE, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
259			case .Full:
260				rl.DrawRectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, rl.GRAY)
261			case .Moving:
262				rl.DrawRectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, rl.DARKGRAY)
263			case .Block:
264				rl.DrawRectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, rl.LIGHTGRAY)
265			case .Fading:
266				rl.DrawRectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, fading_color)
267			}
268			offset.x += SQUARE_SIZE
269		}
270
271		offset.x = controller
272		offset.y += SQUARE_SIZE
273	}
274
275	offset = {500, 45}
276
277	controller = offset.x
278
279	for j in 0..<4 {
280		for i in 0..<4 {
281			#partial switch incoming_piece[i][j] {
282			case .Empty:
283				rl.DrawLine(offset.x, offset.y, offset.x + SQUARE_SIZE, offset.y, rl.LIGHTGRAY)
284				rl.DrawLine(offset.x, offset.y, offset.x, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
285				rl.DrawLine(offset.x + SQUARE_SIZE, offset.y, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
286				rl.DrawLine(offset.x, offset.y + SQUARE_SIZE, offset.x + SQUARE_SIZE, offset.y + SQUARE_SIZE, rl.LIGHTGRAY)
287				offset.x += SQUARE_SIZE
288			case .Moving:
289				rl.DrawRectangle(offset.x, offset.y, SQUARE_SIZE, SQUARE_SIZE, rl.GRAY)
290				offset.x += SQUARE_SIZE
291			}
292		}
293
294		offset.x = controller
295		offset.y += SQUARE_SIZE
296	}
297
298	rl.DrawText("INCOMING:", offset.x, offset.y - 100, 10, rl.GRAY)
299	rl.DrawText(rl.TextFormat("LINES:      %04i", lines), offset.x, offset.y + 20, 10, rl.GRAY)
300
301	if pause {
302		text :: "GAME PAUSED"
303		rl.DrawText(text, SCREEN_WIDTH/2 - rl.MeasureText(text, 40)/2, SCREEN_WIDTH/2 - 40, 40, rl.GRAY)
304	}
305}
306
307
308create_piece :: proc() -> bool {
309	piece_position.x = (GRID_HORIZONTAL_SIZE - 4)/2
310	piece_position.y = 0
311
312	// If the game is starting and you are going to create the first piece, we create an extra one
313	if (begin_play) {
314		get_random_piece()
315		begin_play = false
316	}
317
318	// We assign the incoming piece to the actual piece
319	piece = incoming_piece
320
321	// We assign a random piece to the incoming one
322	get_random_piece()
323
324	// Assign the piece to the grid
325	for i in piece_position.x ..< piece_position.x+4 {
326		for j in 0 ..< 4 {
327			if piece[i - piece_position.x][j] == .Moving {
328				grid[i][j] = .Moving
329			}
330		}
331	}
332
333	return true
334}
335
336get_random_piece :: proc() {
337	random := rl.GetRandomValue(0, 6)
338
339	incoming_piece = {}
340
341	switch random {
342	case 0: incoming_piece[1][1] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[1][2] = .Moving; incoming_piece[2][2] = .Moving //Cube
343	case 1: incoming_piece[1][0] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[1][2] = .Moving; incoming_piece[2][2] = .Moving //L
344	case 2: incoming_piece[1][2] = .Moving; incoming_piece[2][0] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[2][2] = .Moving //L inversa
345	case 3: incoming_piece[0][1] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[3][1] = .Moving //Recta
346	case 4: incoming_piece[1][0] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[1][2] = .Moving; incoming_piece[2][1] = .Moving //Creu tallada
347	case 5: incoming_piece[1][1] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[2][2] = .Moving; incoming_piece[3][2] = .Moving //S
348	case 6: incoming_piece[1][2] = .Moving; incoming_piece[2][2] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[3][1] = .Moving //S inversa
349	}
350}
351
352
353
354delete_complete_lines :: proc() {
355	for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
356		for grid[1][j] == .Fading {
357			for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
358				grid[i][j] = .Empty
359			}
360
361			for j2 := j-1; j2 >= 0; j2 -= 1 {
362				for i2 := 1; i2 < GRID_HORIZONTAL_SIZE-1; i2 += 1 {
363					#partial switch grid[i2][j2] {
364					case .Full:
365						grid[i2][j2+1] = .Full
366						grid[i2][j2] = .Empty
367					case .Fading:
368						grid[i2][j2+1] = .Fading
369						grid[i2][j2] = .Empty
370					}
371				}
372			}
373		}
374
375	}
376}
377
378check_detection :: proc(detection: ^bool) {
379	for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
380		for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
381			if (grid[i][j] == .Moving) && ((grid[i][j+1] == .Full) || (grid[i][j+1] == .Block)) {
382				detection^ = true
383			}
384		}
385	}
386}
387
388resolve_falling_movement :: proc(detection: ^bool, piece_active: ^bool) {
389	if detection^ {
390		for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
391			for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
392				if grid[i][j] == .Moving {
393					grid[i][j] = .Full
394					detection^ = false
395					piece_active^ = false
396				}
397			}
398		}
399	} else {
400		for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
401			for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
402				if grid[i][j] == .Moving {
403					grid[i][j+1] = .Moving
404					grid[i][j] = .Empty
405				}
406			}
407		}
408
409		piece_position.y += 1
410	}
411}
412
413check_completion :: proc(line_to_delete: ^bool) {
414	for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
415		calculator := 0
416		for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
417			if grid[i][j] == .Full {
418				calculator += 1
419			}
420
421			if calculator == GRID_HORIZONTAL_SIZE-2 {
422				line_to_delete^ = true
423				calculator = 0
424
425				for z in 1..<GRID_HORIZONTAL_SIZE-1 {
426					grid[z][j] = .Fading
427				}
428			}
429		}
430	}
431}
432
433resolve_lateral_movement :: proc() -> (collision: bool) {
434	switch {
435	case rl.IsKeyDown(.LEFT):
436		left_collision_loop: for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
437			for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
438				if grid[i][j] == .Moving {
439					if i-1 == 0 || grid[i-1][j] == .Full {
440						collision = true
441						break left_collision_loop
442					}
443				}
444			}
445		}
446
447		if !collision {
448			for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
449				for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
450					if grid[i][j] == .Moving {
451						if grid[i][j] == .Moving {
452							grid[i-1][j] = .Moving
453							grid[i][j] = .Empty
454						}
455					}
456				}
457			}
458
459			piece_position.x -= 1
460		}
461
462
463	case rl.IsKeyDown(.RIGHT):
464		right_collision_loop: for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
465			for i := 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
466				if grid[i][j] == .Moving {
467					if i+1 == GRID_HORIZONTAL_SIZE-1 || grid[i+1][j] == .Full {
468						collision = true
469						break right_collision_loop
470					}
471				}
472			}
473		}
474
475
476		if !collision {
477			for j := GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
478				for i := GRID_HORIZONTAL_SIZE-1; i >= 1; i -= 1 {
479					if grid[i][j] == .Moving {
480						if grid[i][j] == .Moving {
481							grid[i+1][j] = .Moving
482							grid[i][j] = .Empty
483						}
484					}
485				}
486			}
487
488
489			piece_position.x += 1
490		}
491
492	}
493
494	return
495}
496
497resolve_turn_movement :: proc() -> bool {
498	// Input for turning the piece
499	if rl.IsKeyDown(.UP) {
500		checker := false
501
502		// Check all turning possibilities
503		if ((grid[piece_position.x + 3][piece_position.y] == .Moving) &&
504		    (grid[piece_position.x][piece_position.y] != .Empty) &&
505		    (grid[piece_position.x][piece_position.y] != .Moving)) {
506			checker = true
507		} else if ((grid[piece_position.x + 3][piece_position.y + 3] == .Moving) &&
508		           (grid[piece_position.x + 3][piece_position.y] != .Empty) &&
509		           (grid[piece_position.x + 3][piece_position.y] != .Moving)) {
510			checker = true
511		} else if ((grid[piece_position.x][piece_position.y + 3] == .Moving) &&
512		           (grid[piece_position.x + 3][piece_position.y + 3] != .Empty) &&
513		           (grid[piece_position.x + 3][piece_position.y + 3] != .Moving)) {
514			checker = true
515		} else if ((grid[piece_position.x][piece_position.y] == .Moving) &&
516		           (grid[piece_position.x][piece_position.y + 3] != .Empty) &&
517		           (grid[piece_position.x][piece_position.y + 3] != .Moving)) {
518			checker = true
519		} else if ((grid[piece_position.x + 1][piece_position.y] == .Moving) &&
520		           (grid[piece_position.x][piece_position.y + 2] != .Empty) &&
521		           (grid[piece_position.x][piece_position.y + 2] != .Moving)) {
522			checker = true
523		} else if ((grid[piece_position.x + 3][piece_position.y + 1] == .Moving) &&
524		           (grid[piece_position.x + 1][piece_position.y] != .Empty) &&
525		           (grid[piece_position.x + 1][piece_position.y] != .Moving)) {
526			checker = true
527		} else if ((grid[piece_position.x + 2][piece_position.y + 3] == .Moving) &&
528		           (grid[piece_position.x + 3][piece_position.y + 1] != .Empty) &&
529		           (grid[piece_position.x + 3][piece_position.y + 1] != .Moving)) {
530			checker = true
531		} else if ((grid[piece_position.x][piece_position.y + 2] == .Moving) &&
532		           (grid[piece_position.x + 2][piece_position.y + 3] != .Empty) &&
533		           (grid[piece_position.x + 2][piece_position.y + 3] != .Moving)) {
534			checker = true
535		} else if ((grid[piece_position.x + 2][piece_position.y] == .Moving) &&
536		           (grid[piece_position.x][piece_position.y + 1] != .Empty) &&
537		           (grid[piece_position.x][piece_position.y + 1] != .Moving)) {
538			checker = true
539		} else if ((grid[piece_position.x + 3][piece_position.y + 2] == .Moving) &&
540		           (grid[piece_position.x + 2][piece_position.y] != .Empty) &&
541		           (grid[piece_position.x + 2][piece_position.y] != .Moving)) {
542			checker = true
543		} else if ((grid[piece_position.x + 1][piece_position.y + 3] == .Moving) &&
544		           (grid[piece_position.x + 3][piece_position.y + 2] != .Empty) &&
545		           (grid[piece_position.x + 3][piece_position.y + 2] != .Moving)) {
546			checker = true
547		} else if ((grid[piece_position.x][piece_position.y + 1] == .Moving) &&
548		           (grid[piece_position.x + 1][piece_position.y + 3] != .Empty) &&
549		           (grid[piece_position.x + 1][piece_position.y + 3] != .Moving)) {
550			checker = true
551		} else if ((grid[piece_position.x + 1][piece_position.y + 1] == .Moving) &&
552		           (grid[piece_position.x + 1][piece_position.y + 2] != .Empty) &&
553		           (grid[piece_position.x + 1][piece_position.y + 2] != .Moving)) {
554			checker = true
555		} else if ((grid[piece_position.x + 2][piece_position.y + 1] == .Moving) &&
556		           (grid[piece_position.x + 1][piece_position.y + 1] != .Empty) &&
557		           (grid[piece_position.x + 1][piece_position.y + 1] != .Moving)) {
558			checker = true
559		} else if ((grid[piece_position.x + 2][piece_position.y + 2] == .Moving) &&
560		           (grid[piece_position.x + 2][piece_position.y + 1] != .Empty) &&
561		           (grid[piece_position.x + 2][piece_position.y + 1] != .Moving)) {
562			checker = true
563		} else if ((grid[piece_position.x + 1][piece_position.y + 2] == .Moving) &&
564		           (grid[piece_position.x + 2][piece_position.y + 2] != .Empty) &&
565		           (grid[piece_position.x + 2][piece_position.y + 2] != .Moving)) {
566			checker = true
567		}
568
569		if !checker {
570			piece[0][0], piece[3][0], piece[3][3], piece[0][3] = \
571			piece[3][0], piece[3][3], piece[0][3], piece[0][0]
572			
573			piece[1][0], piece[3][1], piece[2][3], piece[0][2] = \
574			piece[3][1], piece[2][3], piece[0][2], piece[1][0]
575
576			piece[2][0], piece[3][2], piece[1][3], piece[0][1] = \
577			piece[3][2], piece[1][3], piece[0][1], piece[2][0]
578			
579			piece[1][1], piece[2][1], piece[2][2], piece[1][2] = \
580			piece[2][1], piece[2][2], piece[1][2], piece[1][1]
581		}
582
583		for j: i32 = GRID_VERTICAL_SIZE-2; j >= 0; j -= 1 {
584			for i: i32 = 1; i < GRID_HORIZONTAL_SIZE-1; i += 1 {
585				if grid[i][j] == .Moving {
586					grid[i][j] = .Empty
587				}
588			}
589		}
590
591		for i in i32(0)..<4 {
592			for j in i32(0)..<4 {
593				if piece[i][j] == .Moving {
594					grid[piece_position.x+i][piece_position.y+j] = .Moving
595				}
596			}
597		}
598
599		return true
600	}
601
602	return false
603}

Declarations Used 23