package main import rl "vendor:raylib" import "vendor:raylib/rlgl" import "core:fmt" import "core:math" import "core:math/ease" import "core:math/rand" import "core:strings" import "core:strconv" // Virtual game field dimensions GameField := Vec2{800, 600} Game :: struct { using state: GameState, lives: u8, pad: Pad, balls: [dynamic]Ball, bricks: [dynamic]Brick, camera: rl.Camera2D, score: u32, levels_done: u32, levelsize: Vec2i, background: rl.Texture } game_init :: proc(prev: ^GameState = nil) -> ^GameState { state := new(Game) state.previous = prev state.variant = state state.draw = game_draw state.update = game_update state.free = game_free 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{ zoom = 1, target = GameField / 2, } game.pad = Pad{ position = Vec2{GameField.x / 2, GameField.y - 40}, size = {80, 20}, health = 100, } game_gen_level(game) } game_init_ball :: proc(game: ^Game) { game_clear_balls(game) append(&game.balls, Ball{ radius = 8, position = Vec2{GameField.x / 2, -100}, is_on_pad = true, pad_offset = Vec2{0, -18}, }) } game_clear_balls :: proc(game: ^Game) { #reverse for &ball, i in game.balls { unordered_remove(&game.balls, i) } } 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} offset : f32 = 0 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)) } } } game_update :: proc(state: ^GameState, delta: f32) { game := transmute(^Game)state using game if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { pause := pause_init(game) stack_push(pause) } if rl.IsKeyPressed(rl.KeyboardKey.P) { for &brick in bricks { brick.is_flying = true } } 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 ball.velocity = rl.Vector2Normalize(direction) * 300 } } all_balls_outside := true #reverse for &ball, i in balls { is_inside := ball_update(&ball, game, delta) if is_inside { all_balls_outside = false } screen_pos := rl.GetWorldToScreen2D(ball.position, camera) if screen_pos.y > WINDOWF.y{ unordered_remove(&balls, i) } } if pad.health <= 0 { go := gameover_init(game) stack_push(go) return } if all_balls_outside { if lives == 0 { go := gameover_init(game) stack_push(go) return } lives -= 1 append(&balls, Ball{ radius = 8, position = Vec2{GameField.x / 2, GameField.y - 40 - 8}, is_on_pad = true, pad_offset = Vec2{0, -18}, }) } only_solid := true #reverse for &brick, i in bricks { brick_update(&brick, game, delta) if brick.type != .SOLID { only_solid = false } if brick.is_to_free { unordered_remove(&bricks, i) } } if only_solid { leveldone := leveldone_init(game) stack_push(leveldone) } explosions_process(delta) camera.offset = WINDOWF / 2 camera.zoom = (WINDOWF.y / GameField.y) / 1.1 } game_draw :: proc(state: ^GameState) { game := transmute(^Game)state rl.BeginMode2D(game.camera) 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) } pad_draw(&game.pad) for &brick, i in game.bricks { brick_draw(&brick) } explosions_draw() rl.EndMode2D() 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) } } game_free :: proc(state: ^GameState) { game := transmute(^Game)state rl.UnloadTexture(game.background) delete(game.bricks) delete(game.balls) free(state) }