Skip to content

Instantly share code, notes, and snippets.

@szTheory
Forked from amirrajan/main.rb
Created December 28, 2021 18:39
Show Gist options
  • Select an option

  • Save szTheory/5fb82c4b6bb1a40161f4ea6bc9ec90db to your computer and use it in GitHub Desktop.

Select an option

Save szTheory/5fb82c4b6bb1a40161f4ea6bc9ec90db to your computer and use it in GitHub Desktop.

Revisions

  1. @amirrajan amirrajan created this gist Dec 28, 2021.
    529 changes: 529 additions & 0 deletions main.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,529 @@
    # Copyright 2021 Scratchwork Development LLC. All rights reserved.

    PI = 3.1415926

    class Game
    attr_gtk

    def tick
    defaults
    render
    input
    calc
    end

    def defaults
    defaults_fiddle
    defaults_game
    end

    def defaults_fiddle
    state.sprite.width = 19 * 1.5
    state.sprite.height = 10 * 1.5
    state.momentum_toward_normal = 1.04
    state.momentum_away_from_normal = 0.96
    state.momentum_decay_out_of_drift = 0.98
    state.momentum_decay_in_drift = 0.99
    state.drift_minimum = 0.1
    state.drift_maximum = 0.5
    state.steering_wheel_range = (PI.fdiv 4).round 4
    state.steering_wheel_delta_in_drift = 3300.0
    state.steering_wheel_delta_out_of_drift = 4800.0
    state.steering_wheel_delta_drifting = (PI.fdiv state.steering_wheel_delta_in_drift).round 4
    state.steering_wheel_delta_not_drifting = (PI.fdiv state.steering_wheel_delta_out_of_drift).round 4
    state.steering_wheel_reset_perc = 0.5
    state.camera.scale = 1.5
    end

    def defaults_game
    return if state.tick_count != 0
    load_map!
    reset_game!
    end

    def render
    outputs.background_color = [0, 0, 0]
    render_won
    render_game
    render_instructions
    end

    def render_won
    return if !state.won

    outputs.labels << { x: 640, y: 380, text: "you win!", size_enum: 5, alignment_enum: 1, r: 255, g: 255, b: 255 }
    outputs.labels << { x: 640, y: 340, text: "#{'%.2f' % (state.clock.fdiv 60)}s", size_enum: 5, r: 255, g: 255, b: 255, alignment_enum: 1 }
    outputs.labels << { x: 640, y: 300, text: "(press 'r' to go again)", size_enum: 5, r: 255, g: 255, b: 255, alignment_enum: 1 }
    end

    def render_god_mode
    return if state.won
    return if state.god_mode != :enabled

    outputs[:scene].borders << { x: inputs.mouse.x - 30, y: inputs.mouse.y - 30, w: 60, h: 60, g: 255 }
    outputs[:scene].borders << state.track_rects.map { |t| relative_to_car t.merge(g: 255) }
    outputs[:scene].borders << (relative_to_car car_collision_rect)
    end

    def render_game
    return if state.won

    outputs[:scene].w = 2560
    outputs[:scene].h = 1440
    outputs[:scene].background_color = [0, 0, 0, 0]
    outputs[:scene].primitives << sprites_map_viewport
    outputs[:scene].primitives << state.smoke.map { |smoke| relative_to_car smoke }
    outputs[:scene].primitives << sprites_car.merge(x: sprites_car.x + 640, y: sprites_car.y + 360)
    outputs[:scene].primitives << state.deaths.flat_map do |stones|
    stones.map { |stone| relative_to_car stone }
    end

    if state.god_mode == :enabled
    outputs.sprites << { x: 0,
    y: 0,
    w: 2560,
    h: 1440,
    path: :scene, blendmode_enum: 0 }
    else
    outputs.sprites << { x: 0 + sprites_scene_offset_x,
    y: 0 + sprites_scene_offset_y,
    w: 2560 * state.camera.scale,
    h: 1440 * state.camera.scale,
    path: :scene, blendmode_enum: 0 }
    end
    end

    def render_instructions
    outputs.labels << { x: 10,
    y: 30,
    text: "controls - turn: left/right arrow keys, drift: spacebar",
    size_enum: 1,
    alignment_enum: 0,
    r: 255,
    g: 255,
    b: 255 }
    end

    def input
    input_game
    input_god_mode
    end

    def input_game
    reset_game! if inputs.keyboard.key_down.r
    return if state.clock < 30

    turn_magnitude = 1.0

    if inputs.controller_one.left_analog_x_perc.abs > 0
    turn_magnitude = inputs.controller_one.left_analog_x_perc * 2.0
    if turn_magnitude > 1.0
    turn_magnitude = 1.0
    elsif turn_magnitude < -1.0
    turn_magnitude = -1.0
    end
    end

    drift_down = !!inputs.keyboard.space ||
    !!inputs.controller_one.r1 ||
    !!inputs.controller_one.r2

    left_down = !!inputs.left || !!inputs.keyboard.j
    right_down = !!inputs.right || !!inputs.keyboard.k

    state.turn_magnitude = turn_magnitude

    if left_finger
    if left_finger.x < 320
    outputs.borders << { x: 0, y: 0, w: 320, h: 720, r: 255, g: 255, b: 255, a: 128 }
    left_down = true
    right_down = false
    elsif left_finger.x > 320
    outputs.borders << { x: 320, y: 0, w: 320, h: 720, r: 255, g: 255, b: 255, a: 128 }
    left_down = false
    right_down = true
    end
    end

    if right_finger
    outputs.borders << { x: 640, y: 0, w: 640, h: 720, r: 255, g: 255, b: 255, a: 128 }
    drift_down = true
    end

    if left_down
    state.steering = :left
    elsif right_down
    state.steering = :right
    else
    state.steering = :released
    end

    if drift_down
    state.drift_mode = :on
    else
    state.drift_mode = :off
    end
    end

    def input_god_mode
    input_god_mode_enabled
    input_god_mode_toggle
    end

    def input_god_mode_enabled
    return if state.god_mode != :enabled
    input_god_mode_enabled_mouse
    input_god_mode_enabled_keyboard
    end

    def input_god_mode_enabled_mouse
    return if !inputs.mouse.click

    if inputs.keyboard.x
    to_delete = state.collision_points.find do |r|
    inputs.mouse.inside_rect? (relative_to_car x: r.x - 30, y: r.y - 30, w: 60, h: 60)
    end

    if to_delete
    state.collision_points.reject! { |p| p.x == to_delete.x && p.y == to_delete.y }
    state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
    save_map!
    end
    else
    point = [inputs.mouse.x + state.x - 640, inputs.mouse.y + state.y - 360]
    state.collision_points << point
    state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
    state.x = point.x
    state.y = point.y
    save_map!
    end
    end

    def input_god_mode_enabled_keyboard
    load_map! if inputs.keyboard.key_down.e
    if inputs.keyboard.space
    state.angle_r += inputs.left_right * 0.01
    else
    state.y += inputs.keyboard.up_down * 5
    state.x += inputs.keyboard.left_right * 5
    end
    end

    def input_god_mode_toggle
    return if !inputs.keyboard.key_down.g

    if state.god_mode == :disabled
    state.god_mode = :enabled
    else
    state.god_mode = :disabled
    end
    end

    def calc
    return if state.god_mode == :enabled
    return if state.won
    state.clock += 1
    calc_camera
    calc_steering
    calc_physics
    calc_smoke
    calc_car
    calc_prism_stones
    calc_game_over
    end

    def calc_camera
    state.camera.target_center_x = -((state.angle_r.to_degrees + 90).vector_x * 340)
    state.camera.target_center_y = ((state.angle_r.to_degrees + 90).vector_y * 260)
    state.camera.center_x += (state.camera.target_center_x - state.camera.center_x) * 0.01
    state.camera.center_y += (state.camera.target_center_y - state.camera.center_y) * 0.01
    end

    def calc_steering
    case state.steering
    when :right
    if state.steer_angle_r > state.steering_wheel_range * -1.0
    state.steer_angle_r = state.steer_angle_r - steering_wheel_delta * state.turn_magnitude.abs
    end
    when :left
    if state.steer_angle_r < state.steering_wheel_range
    state.steer_angle_r = state.steer_angle_r + steering_wheel_delta * state.turn_magnitude.abs
    end
    when :released
    if state.steer_angle_r < 0
    state.steer_angle_r = state.steer_angle_r + steering_wheel_delta * state.steering_wheel_reset_perc
    elsif state.steer_angle_r > 0
    state.steer_angle_r = state.steer_angle_r - steering_wheel_delta * state.steering_wheel_reset_perc
    end
    end

    state.steer_angle_r = state.steer_angle_r.round(4).to_f
    end

    def calc_physics
    if state.drift_mode == :on
    if state.steer_angle_r > 0
    state.drift_percentage_right *= state.momentum_toward_normal
    state.drift_percentage_left *= state.momentum_away_from_normal
    elsif state.steer_angle_r < 0
    state.drift_percentage_right *= state.momentum_away_from_normal
    state.drift_percentage_left *= state.momentum_toward_normal
    else
    state.drift_percentage_right *= state.momentum_decay_out_of_drift
    state.drift_percentage_left *= state.momentum_decay_out_of_drift
    end
    else
    state.drift_percentage_right *= state.momentum_decay_out_of_drift
    state.drift_percentage_left *= state.momentum_decay_out_of_drift
    end

    state.drift_percentage_right = state.drift_minimum if state.drift_mode == :on && state.drift_percentage_right < state.drift_minimum
    state.drift_percentage_left = state.drift_minimum if state.drift_mode == :on && state.drift_percentage_left < state.drift_minimum

    state.drift_percentage_right = state.drift_maximum if state.drift_percentage_right > state.drift_maximum
    state.drift_percentage_left = state.drift_maximum if state.drift_percentage_left > state.drift_maximum

    state.drift_percentage_right = state.drift_percentage_right.round(4)
    state.drift_percentage_left = state.drift_percentage_left.round(4)
    end

    def calc_smoke
    state.smoke.each do |p|
    p.w += 2
    p.h += 2
    p.x -= 1
    p.y -= 1
    p.x -= p.dx
    p.y -= p.dy
    p.a -= 2
    end
    state.smoke.reject! { |p| p.a <= 0 }
    return if state.drift_mode == :off
    return if !state.tick_count.zmod?(5)
    state.smoke << sprites_smoke_new
    end

    def calc_car
    velocity_x = state.speed * Math.sin(state.angle_r + state.steer_angle_r) * (1.0 - state.drift_percentage_right - state.drift_percentage_left)
    velocity_y = state.speed * Math.cos(state.angle_r + state.steer_angle_r) * (1.0 - state.drift_percentage_right - state.drift_percentage_left)

    normal_x_right = state.speed * Math.sin((PI / 2.0 ) - state.angle_r) * state.drift_percentage_right
    normal_y_right = state.speed * Math.cos((PI / 2.0 ) - state.angle_r) * state.drift_percentage_right * -1.0

    normal_x_left = state.speed * Math.sin((PI / 2.0 ) - state.angle_r) * state.drift_percentage_left * -1.0
    normal_y_left = state.speed * Math.cos((PI / 2.0 ) - state.angle_r) * state.drift_percentage_left

    state.x += velocity_x + normal_x_right + normal_x_left
    state.y += velocity_y + normal_y_right + normal_y_left

    state.angle_r -= state.steer_angle_r
    end

    def calc_prism_stones
    state.deaths.each do |stones|
    stones.each do |stone|
    stone.w ||= 8
    stone.h ||= 8
    stone.x ||= stone.original_x
    stone.y ||= stone.original_y
    stone.path ||= "sprites/#{stone.type}-stone.png"
    stone.a = ((stone.t - stone.current_t).fdiv stone.t) * 255
    stone.lifetime ||= 300 * 60
    stone.lifetime -= 1
    if !stone.still
    if stone.current_t == stone.t
    stone.x = stone.original_x
    stone.y = stone.original_y
    stone.current_t = 0
    else
    stone.y += stone.dy
    stone.x += stone.dx
    stone.current_t += 1
    end
    end
    end
    end
    end

    def calc_game_over
    if state.track_rects.length > 1 && (state.track_rects.last.intersect_rect? car_collision_rect)
    state.won = true
    elsif !state.track_rects.any? { |c| c.intersect_rect? car_collision_rect }
    state.deaths << new_prism_stones if state.x != 155 && state.y != 1258
    reset_game!
    end
    end

    def reset_game!
    state.deaths ||= []
    state.deaths.reject! { |d| d.any? { |s| s.lifetime && s.lifetime <= 0 } }
    state.god_mode = :disabled
    state.x = 155
    state.y = 730
    state.speed = 1
    state.angle_r = 0
    state.steering = :released
    state.steer_angle_r = 0.0
    state.drift_percentage_right = 0.0
    state.drift_percentage_left = 0.0
    state.drift_mode = :off
    state.speed = 4.0
    state.clock = 0
    state.won = false
    state.drift_percentage_left = 0
    state.drift_percentage_right = 0
    state.drift_sound_debounce = 0
    state.car_after_images = []
    state.smoke = []
    state.camera.target_center_x = 0
    state.camera.target_center_y = 0
    state.camera.center_x = 0
    state.camera.center_y = 0
    end

    def steering_wheel_delta
    return state.steering_wheel_delta_drifting if state.drift_mode == :on
    return state.steering_wheel_delta_not_drifting
    end

    def sprites_car
    {
    x: -state.sprite.width.half,
    y: -state.sprite.height.half,
    w: state.sprite.width,
    h: state.sprite.height,
    path: 'sprites/86.png',
    angle: 90 + (state.angle_r.to_degrees * -1),
    rotation_anchor_x: 0.7,
    rotation_anchor_y: 0.5,
    }
    end

    def sprites_car_after_image
    sprites_car.merge(x: state.x - state.sprite.width.half,
    y: state.y - state.sprite.height.half,
    angle: 90 + (state.angle_r.to_degrees * -1),
    a: 200)
    end

    def sprites_smoke_new
    angle = 90 + (state.angle_r.to_degrees * -1)
    sprites_car.merge x: state.x - state.sprite.width.half,
    y: state.y - state.sprite.height.half,
    dx: angle.vector_x * 0.5,
    dy: angle.vector_y * 0.5,
    angle: angle,
    a: 255,
    path: ["sprites/smoke-1.png", "sprites/smoke-2.png", "sprites/smoke-3.png"].sample
    end

    def car_collision_rect
    {
    x: state.x - 6,
    y: state.y - 6,
    w: 12,
    h: 12,
    r: 255
    }
    end

    def sprites_map
    {
    x: 0,
    y: 0,
    w: 6400,
    h: 6400,
    path: 'sprites/map.png'
    }
    end

    def load_map!
    state.collision_points = (gtk.deserialize_state 'data/map.txt').collision_points if gtk.read_file 'data/map.txt'
    state.track_rects = state.collision_points.map { |p| { x: p.x - 30, y: p.y - 30, w: 60, h: 60 } }
    end

    def save_map!
    gtk.serialize_state 'data/map.txt', state
    end

    def relative_to_car point
    return nil if !point.x || !point.y
    point.merge x: point.x - state.x + 640,
    y: point.y - state.y + 360
    end

    def new_prism_stones
    c = [:blue, :red, :yellow, :teal, :green, :white].sample
    [
    { original_x: state.x, original_y: state.y, w: 8, h: 8,
    type: c, current_t: 0, t: 120, dy: 0, dx: 0, still: true },
    { original_x: state.x - 1, original_y: state.y,
    type: c, current_t: 0, t: 120, dy: 0.25, dx: 0 },
    { original_x: state.x + 1, original_y: state.y,
    type: c, current_t: 0, t: 137, dy: 0.2, dx: 0 }
    ]
    end

    def sprites_scene_offset_x
    -((1280 * state.camera.scale) - 1280).half - state.camera.center_x
    end

    def sprites_scene_offset_y
    -(( 720 * state.camera.scale) - 720).half - state.camera.center_y
    end

    def sprites_map_viewport
    x = 0
    y = 0
    source_x = state.x - 640
    source_y = state.y - 360

    if state.x < 640
    source_x = 0
    x = 640 - state.x
    end

    if state.y < 720
    source_y = 0
    y = 360 - state.y
    end

    {
    x: x,
    y: y,
    w: 2560,
    h: 1440,
    source_x: source_x,
    source_y: source_y,
    source_w: 2560,
    source_h: 1440,
    path: 'sprites/map.png'
    }
    end

    def left_finger
    if inputs.finger_one && inputs.finger_one.x < 640
    return inputs.finger_one
    elsif inputs.finger_two && inputs.finger_two.x < 640
    return inputs.finger_two
    else
    return nil
    end
    end

    def right_finger
    if inputs.finger_one && inputs.finger_one.x > 640
    return inputs.finger_one
    elsif inputs.finger_two && inputs.finger_two.x > 640
    return inputs.finger_two
    else
    return nil
    end
    end
    end

    def tick args
    $game ||= Game.new
    $game.args = args
    $game.tick
    end