module main
import (
std.math math
vendor.raylib rl
)
let calculate_flap_velocity(): f64 = math.sqrt(-2.0 * gravity * flap_height)
let random_pipe_gap_y(): f64 {
let margin: f64 = 0.05
let minimum_gap_y = base_height_fraction + pipe_gap_size / 2.0 + margin
let maximum_gap_y = 1.0 - pipe_gap_size / 2.0 - margin
let random_value = rl.get_random_value(0, 1000).(f64) / 1000.0
return minimum_gap_y + random_value * (maximum_gap_y - minimum_gap_y)
}
let initialise_pipes(pipes: []mut Pipe) {
let first_pipe_x: f64 = 1.2
for let mut i: usz = 0; i < @len(pipes); i += 1 {
pipes[i].x = first_pipe_x + i.(f64) * pipe_spacing
pipes[i].gap_y = random_pipe_gap_y()
pipes[i].passed = false
}
}
let update_bird(state: *mut GameState, assets: *GameAssets, flap: bool) {
if flap {
state.bird_velocity_y = calculate_flap_velocity()
rl.play_sound(assets.wing)
}
state.bird_velocity_y = max(
state.bird_velocity_y + gravity * state.delta_time,
terminal_velocity,
)
state.bird_y = state.bird_y + state.bird_velocity_y * state.delta_time
let floor_y = bird_size / 2.0
let ceiling_y = 1.0 - bird_size / 2.0
if state.bird_y <= floor_y {
state.bird_y = floor_y
state.bird_velocity_y = 0.0
state.lost = true
return
}
if state.bird_y >= ceiling_y {
state.bird_y = ceiling_y
state.bird_velocity_y = 0.0
state.lost = true
}
}
let find_rightmost_pipe_x(pipes: []Pipe): f64 {
let mut rightmost_x = pipes[0].x
for pipe in pipes {
rightmost_x = max(rightmost_x, pipe.x)
}
return rightmost_x
}
let update_pipes(state: *mut GameState): bool {
let speed_multiplier = if state.double_pipe_speed {
double_pipe_speed_multiplier
} else {
1.0
}
let movement = pipe_speed * speed_multiplier * state.delta_time
let mut passed_pipe = false
for let mut i: usz = 0; i < @len(state.pipes); i += 1 {
state.pipes[i].x = state.pipes[i].x - movement
if !state.pipes[i].passed && state.pipes[i].x < bird_x {
state.pipes[i].passed = true
passed_pipe = true
}
}
let pipe_half_width = pipe_width / 2.0
let mut rightmost_x = find_rightmost_pipe_x(state.pipes)
// Reuse pipes that have left the screen instead of shifting or allocating.
for let mut i: usz = 0; i < @len(state.pipes); i += 1 {
if state.pipes[i].x + pipe_half_width < 0.0 {
state.pipes[i].x = rightmost_x + pipe_spacing
state.pipes[i].gap_y = random_pipe_gap_y()
state.pipes[i].passed = false
rightmost_x = state.pipes[i].x
}
}
if passed_pipe {
state.points += if state.double_pipe_speed {
double_pipe_points_multiplier
} else {
1
}
}
return passed_pipe
}
let reset_game(state: *mut GameState) {
state.bird_y = 0.5
state.bird_velocity_y = 0.0
state.delta_time = 0.0
state.points = 0
state.lost = false
state.time_since_loss = 0.0
initialise_pipes(state.pipes)
}
let play_loss_sounds(assets: *GameAssets) {
rl.play_sound(assets.hit)
rl.play_sound(assets.die)
}
let update(state: *mut GameState, assets: *GameAssets) {
state.delta_time = rl.get_frame_time()
let action_pressed = rl.is_key_pressed(.Space) || rl.is_mouse_button_pressed(.Left)
if state.started && !state.lost && rl.is_key_pressed(.Escape) {
state.paused = !state.paused
}
if rl.is_key_pressed(.F3) {
state.show_collision_boxes = !state.show_collision_boxes
}
if (!state.started || state.lost) && rl.is_key_pressed(.F9) {
state.double_pipe_speed = !state.double_pipe_speed
}
if !state.started {
if action_pressed {
state.started = true
} else {
return
}
}
if state.paused && action_pressed {
state.paused = false
}
if state.paused {
return
}
if state.lost {
state.time_since_loss += state.delta_time
if state.time_since_loss < restart_grace_period || !action_pressed {
return
}
reset_game(state)
}
state.background_scroll = state.background_scroll + pipe_speed * background_scroll_speed * state.delta_time
if state.background_scroll >= 1.0 {
state.background_scroll = state.background_scroll - 1.0
}
state.base_scroll = state.base_scroll + pipe_speed * base_scroll_speed * state.delta_time
if state.base_scroll >= 1.0 {
state.base_scroll = state.base_scroll - 1.0
}
update_bird(state, assets, action_pressed)
if state.lost {
play_loss_sounds(assets)
return
}
if update_pipes(state) {
rl.play_sound(assets.point)
}
if bird_collides_with_pipes(state) {
state.lost = true
play_loss_sounds(assets)
}
}