1package main
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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() {
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
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
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
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
186 if rl.IsKeyDown(.DOWN) && fast_fall_movement_counter >= FAST_FALL_AWAIT_COUNTER {
187
188 gravity_movement_counter += inverse_gravity_speed
189 }
190
191 if gravity_movement_counter >= inverse_gravity_speed {
192
193 check_detection(&detection)
194
195
196 resolve_falling_movement(&detection, &piece_active)
197
198
199 check_completion(&line_to_delete)
200
201 gravity_movement_counter = 0
202 }
203
204
205 if lateral_movement_counter >= LATERAL_SPEED {
206
207 if !resolve_lateral_movement() {
208 lateral_movement_counter = 0
209 }
210 }
211
212
213 if turn_movement_counter >= TURNING_SPEED {
214
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
313 if (begin_play) {
314 get_random_piece()
315 begin_play = false
316 }
317
318
319 piece = incoming_piece
320
321
322 get_random_piece()
323
324
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
343 case 1: incoming_piece[1][0] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[1][2] = .Moving; incoming_piece[2][2] = .Moving
344 case 2: incoming_piece[1][2] = .Moving; incoming_piece[2][0] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[2][2] = .Moving
345 case 3: incoming_piece[0][1] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[3][1] = .Moving
346 case 4: incoming_piece[1][0] = .Moving; incoming_piece[1][1] = .Moving; incoming_piece[1][2] = .Moving; incoming_piece[2][1] = .Moving
347 case 5: incoming_piece[1][1] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[2][2] = .Moving; incoming_piece[3][2] = .Moving
348 case 6: incoming_piece[1][2] = .Moving; incoming_piece[2][2] = .Moving; incoming_piece[2][1] = .Moving; incoming_piece[3][1] = .Moving
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
499 if rl.IsKeyDown(.UP) {
500 checker := false
501
502
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}