2024-09-04 22:43:27 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import rl "vendor:raylib"
|
2024-09-05 23:42:18 +03:00
|
|
|
import "vendor:raylib/rlgl"
|
2024-09-05 22:39:52 +03:00
|
|
|
import "core:fmt"
|
2024-09-08 03:39:03 +03:00
|
|
|
import "core:math"
|
2024-09-09 00:18:35 +03:00
|
|
|
import "core:math/ease"
|
2024-09-09 23:41:16 +03:00
|
|
|
import "core:math/rand"
|
|
|
|
import "core:strings"
|
|
|
|
import "core:strconv"
|
2024-09-04 22:43:27 +03:00
|
|
|
|
2024-09-05 23:42:18 +03:00
|
|
|
// Virtual game field dimensions
|
|
|
|
GameField := Vec2{800, 600}
|
|
|
|
|
2024-09-04 22:43:27 +03:00
|
|
|
Game :: struct {
|
|
|
|
using state: GameState,
|
|
|
|
lives: u8,
|
|
|
|
pad: Pad,
|
|
|
|
balls: [dynamic]Ball,
|
|
|
|
bricks: [dynamic]Brick,
|
2024-09-09 00:18:35 +03:00
|
|
|
camera: rl.Camera2D,
|
2024-09-09 23:41:16 +03:00
|
|
|
score: u32,
|
|
|
|
levels_done: u32,
|
|
|
|
|
|
|
|
levelsize: Vec2i,
|
|
|
|
|
|
|
|
background: rl.Texture
|
2024-09-04 22:43:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
|
2024-09-09 00:18:35 +03:00
|
|
|
game_init :: proc(prev: ^GameState = nil) -> ^GameState {
|
2024-09-05 22:39:52 +03:00
|
|
|
state := new(Game)
|
2024-09-09 23:41:16 +03:00
|
|
|
state.previous = prev
|
2024-09-05 22:39:52 +03:00
|
|
|
state.variant = state
|
|
|
|
state.draw = game_draw
|
|
|
|
state.update = game_update
|
2024-09-09 00:18:35 +03:00
|
|
|
state.free = game_free
|
2024-09-09 23:41:16 +03:00
|
|
|
state.levelsize = Vec2i{6, 4}
|
|
|
|
|
|
|
|
backimage := rl.GenImageChecked(800, 600, 80, 60, rl.Color{70, 20, 120, 255}, rl.Color{80, 30, 130, 255})
|
|
|
|
defer rl.UnloadImage(backimage)
|
|
|
|
state.background = rl.LoadTextureFromImage(backimage)
|
|
|
|
|
|
|
|
game_setup(state)
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
|
|
|
game_setup :: proc(game: ^Game) {
|
|
|
|
#reverse for ball, i in game.balls {
|
|
|
|
unordered_remove(&game.balls, i)
|
|
|
|
}
|
|
|
|
#reverse for brick, i in game.bricks {
|
|
|
|
unordered_remove(&game.bricks, i)
|
|
|
|
}
|
|
|
|
game.lives = 3
|
|
|
|
game.camera = rl.Camera2D{
|
2024-09-05 23:42:18 +03:00
|
|
|
zoom = 1,
|
|
|
|
target = GameField / 2,
|
|
|
|
}
|
2024-09-09 23:41:16 +03:00
|
|
|
game.pad = Pad{
|
2024-09-05 23:42:18 +03:00
|
|
|
position = Vec2{GameField.x / 2, GameField.y - 40},
|
2024-09-09 23:41:16 +03:00
|
|
|
size = {80, 20},
|
|
|
|
health = 100,
|
2024-09-04 22:43:27 +03:00
|
|
|
}
|
2024-09-09 23:41:16 +03:00
|
|
|
game_gen_level(game)
|
|
|
|
}
|
|
|
|
|
|
|
|
game_init_ball :: proc(game: ^Game) {
|
|
|
|
game_clear_balls(game)
|
|
|
|
append(&game.balls, Ball{
|
2024-09-08 03:39:03 +03:00
|
|
|
radius = 8,
|
2024-09-09 23:41:16 +03:00
|
|
|
position = Vec2{GameField.x / 2, -100},
|
2024-09-08 03:39:03 +03:00
|
|
|
is_on_pad = true,
|
2024-09-09 23:41:16 +03:00
|
|
|
pad_offset = Vec2{0, -18},
|
2024-09-08 03:39:03 +03:00
|
|
|
})
|
2024-09-09 23:41:16 +03:00
|
|
|
}
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
game_clear_balls :: proc(game: ^Game) {
|
|
|
|
#reverse for &ball, i in game.balls {
|
|
|
|
unordered_remove(&game.balls, i)
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
game_clear_bricks :: proc(game: ^Game) {
|
|
|
|
#reverse for &brick, i in game.bricks {
|
|
|
|
unordered_remove(&game.bricks, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
game_gen_level :: proc(game: ^Game) {
|
|
|
|
explosions_init()
|
|
|
|
game_init_ball(game)
|
|
|
|
game_clear_bricks(game)
|
|
|
|
|
|
|
|
brick_size := Vec2{40, 30}
|
|
|
|
brick_margin := brick_size + 10
|
|
|
|
center := Vec2{GameField.x / 2, 100}
|
2024-09-09 00:18:35 +03:00
|
|
|
|
2024-09-08 03:39:03 +03:00
|
|
|
offset : f32 = 0
|
2024-09-09 23:41:16 +03:00
|
|
|
for y : i32 = 0; y < game.levelsize.y; y += 1 {
|
|
|
|
for x : i32 = 0; x < game.levelsize.x / 2; x += 1{
|
|
|
|
choice := rand.choice([]Brick_Type{.NORMAL, .NORMAL, .NORMAL, .HARD, .HARD, .SOLID, .EXPLOSIVE, .BONUS})
|
|
|
|
append(&game.bricks, spawn_brick(center + {-brick_margin.x / 2 - f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice))
|
|
|
|
append(&game.bricks, spawn_brick(center + {+brick_margin.x / 2 + f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice))
|
2024-09-08 03:39:03 +03:00
|
|
|
}
|
|
|
|
}
|
2024-09-09 23:41:16 +03:00
|
|
|
|
2024-09-04 22:43:27 +03:00
|
|
|
}
|
|
|
|
|
2024-09-05 22:39:52 +03:00
|
|
|
game_update :: proc(state: ^GameState, delta: f32) {
|
|
|
|
game := transmute(^Game)state
|
|
|
|
|
|
|
|
using game
|
|
|
|
|
2024-09-09 17:48:24 +03:00
|
|
|
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
|
|
|
|
pause := pause_init(game)
|
|
|
|
stack_push(pause)
|
|
|
|
}
|
2024-09-09 23:41:16 +03:00
|
|
|
if rl.IsKeyPressed(rl.KeyboardKey.P) {
|
|
|
|
for &brick in bricks {
|
|
|
|
brick.is_flying = true
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 03:39:03 +03:00
|
|
|
pad_update(&game.pad, delta)
|
|
|
|
if rl.IsKeyPressed(rl.KeyboardKey.SPACE) {
|
|
|
|
for &ball, i in balls {
|
|
|
|
if !ball.is_on_pad { continue }
|
|
|
|
|
|
|
|
direction := Vec2{pad.velocity.x / PAD_MAX_SPEED, -1}
|
|
|
|
ball.is_on_pad = false
|
2024-09-09 19:05:38 +03:00
|
|
|
ball.velocity = rl.Vector2Normalize(direction) * 300
|
2024-09-08 03:39:03 +03:00
|
|
|
}
|
2024-09-05 22:39:52 +03:00
|
|
|
}
|
2024-09-08 03:39:03 +03:00
|
|
|
|
|
|
|
all_balls_outside := true
|
|
|
|
|
|
|
|
#reverse for &ball, i in balls {
|
|
|
|
is_inside := ball_update(&ball, game, delta)
|
|
|
|
if is_inside { all_balls_outside = false }
|
|
|
|
|
2024-09-09 00:18:35 +03:00
|
|
|
screen_pos := rl.GetWorldToScreen2D(ball.position, camera)
|
2024-09-09 19:05:38 +03:00
|
|
|
if screen_pos.y > WINDOWF.y{
|
2024-09-08 03:39:03 +03:00
|
|
|
unordered_remove(&balls, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
if pad.health <= 0 {
|
|
|
|
|
|
|
|
go := gameover_init(game)
|
|
|
|
stack_push(go)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-08 03:39:03 +03:00
|
|
|
if all_balls_outside {
|
2024-09-09 23:41:16 +03:00
|
|
|
if lives == 0 {
|
|
|
|
go := gameover_init(game)
|
|
|
|
stack_push(go)
|
|
|
|
return
|
|
|
|
}
|
2024-09-08 03:39:03 +03:00
|
|
|
lives -= 1
|
|
|
|
append(&balls, Ball{
|
|
|
|
radius = 8,
|
|
|
|
position = Vec2{GameField.x / 2, GameField.y - 40 - 8},
|
|
|
|
is_on_pad = true,
|
2024-09-09 23:41:16 +03:00
|
|
|
pad_offset = Vec2{0, -18},
|
2024-09-08 03:39:03 +03:00
|
|
|
})
|
2024-09-05 22:39:52 +03:00
|
|
|
}
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
only_solid := true
|
2024-09-08 03:39:03 +03:00
|
|
|
#reverse for &brick, i in bricks {
|
2024-09-09 23:41:16 +03:00
|
|
|
brick_update(&brick, game, delta)
|
|
|
|
if brick.type != .SOLID {
|
|
|
|
only_solid = false
|
|
|
|
}
|
2024-09-09 00:18:35 +03:00
|
|
|
if brick.is_to_free {
|
2024-09-08 03:39:03 +03:00
|
|
|
unordered_remove(&bricks, i)
|
|
|
|
}
|
|
|
|
}
|
2024-09-05 23:42:18 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
if only_solid {
|
|
|
|
leveldone := leveldone_init(game)
|
|
|
|
stack_push(leveldone)
|
|
|
|
}
|
|
|
|
|
|
|
|
explosions_process(delta)
|
|
|
|
|
2024-09-08 03:39:03 +03:00
|
|
|
camera.offset = WINDOWF / 2
|
|
|
|
camera.zoom = (WINDOWF.y / GameField.y) / 1.1
|
2024-09-04 22:43:27 +03:00
|
|
|
}
|
|
|
|
|
2024-09-05 22:39:52 +03:00
|
|
|
game_draw :: proc(state: ^GameState) {
|
|
|
|
game := transmute(^Game)state
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
rl.BeginMode2D(game.camera)
|
2024-09-05 23:42:18 +03:00
|
|
|
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
rl.DrawTextureV(game.background, {}, rl.WHITE)
|
|
|
|
// rl.DrawRectangleGradientV(0, 0, i32(GameField.x), i32(GameField.y), rl.RED, rl.RAYWHITE)
|
|
|
|
for &ball, i in game.balls {
|
|
|
|
ball_draw(&ball)
|
2024-09-08 03:39:03 +03:00
|
|
|
}
|
2024-09-09 23:41:16 +03:00
|
|
|
pad_draw(&game.pad)
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
for &brick, i in game.bricks {
|
|
|
|
brick_draw(&brick)
|
2024-09-08 03:39:03 +03:00
|
|
|
}
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
explosions_draw()
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-05 23:42:18 +03:00
|
|
|
rl.EndMode2D()
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
game_field_zoomed := GameField * game.camera.zoom
|
|
|
|
|
|
|
|
buf : [12]byte
|
|
|
|
score_string := strconv.itoa(buf[:], int(game.score))
|
|
|
|
score_ui := strings.right_justify(score_string, 8, "0")
|
|
|
|
score_ui_c := strings.unsafe_string_to_cstring(score_ui)
|
|
|
|
|
|
|
|
size := rl.MeasureTextEx(FontUI, score_ui_c, 48, 2)
|
|
|
|
rl.DrawTextPro(FontUI, score_ui_c, {WINDOWF.x / 2, WINDOWF.y - 28}, size / 2, 0, 48, 2, rl.WHITE)
|
|
|
|
|
|
|
|
level_string := rl.TextFormat("Уровень %d", game.levels_done + 1)
|
|
|
|
size = rl.MeasureTextEx(FontUI, level_string, 48, 2)
|
|
|
|
rl.DrawTextPro(FontUI, level_string, {WINDOWF.x / 2 + game_field_zoomed.x / 2, WINDOWF.y - 28}, {size.x, size.y / 2}, 0, 48, 2, rl.WHITE)
|
|
|
|
|
|
|
|
for i : u8 = 0; i < game.lives; i += 1 {
|
|
|
|
draw_ball_virtual({WINDOWF.x / 2 - game_field_zoomed.x / 2 + 16 + f32(i * 40), WINDOWF.y - 28}, 16)
|
2024-09-08 03:39:03 +03:00
|
|
|
}
|
2024-09-09 00:18:35 +03:00
|
|
|
}
|
|
|
|
|
2024-09-08 03:39:03 +03:00
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
|
2024-09-09 00:18:35 +03:00
|
|
|
game_free :: proc(state: ^GameState) {
|
|
|
|
game := transmute(^Game)state
|
|
|
|
|
2024-09-09 23:41:16 +03:00
|
|
|
rl.UnloadTexture(game.background)
|
|
|
|
|
2024-09-09 00:18:35 +03:00
|
|
|
delete(game.bricks)
|
|
|
|
delete(game.balls)
|
|
|
|
|
|
|
|
free(state)
|
2024-09-04 22:43:27 +03:00
|
|
|
}
|
2024-09-09 00:18:35 +03:00
|
|
|
|
|
|
|
|