summaryrefslogtreecommitdiffhomepage
path: root/samples/99_genre_arcade/dueling_starships/app/main.rb
diff options
context:
space:
mode:
Diffstat (limited to 'samples/99_genre_arcade/dueling_starships/app/main.rb')
-rw-r--r--samples/99_genre_arcade/dueling_starships/app/main.rb365
1 files changed, 365 insertions, 0 deletions
diff --git a/samples/99_genre_arcade/dueling_starships/app/main.rb b/samples/99_genre_arcade/dueling_starships/app/main.rb
new file mode 100644
index 0000000..8adcf3d
--- /dev/null
+++ b/samples/99_genre_arcade/dueling_starships/app/main.rb
@@ -0,0 +1,365 @@
+class DuelingSpaceships
+ attr_accessor :state, :inputs, :outputs, :grid
+
+ def tick
+ defaults
+ render
+ calc
+ input
+ end
+
+ def defaults
+ outputs.background_color = [0, 0, 0]
+ state.ship_blue ||= new_blue_ship
+ state.ship_red ||= new_red_ship
+ state.flames ||= []
+ state.bullets ||= []
+ state.ship_blue_score ||= 0
+ state.ship_red_score ||= 0
+ state.stars ||= 100.map do
+ [rand.add(2).to_square(grid.w_half.randomize(:sign, :ratio),
+ grid.h_half.randomize(:sign, :ratio)),
+ 128 + 128.randomize(:ratio), 255, 255]
+ end
+ end
+
+ def default_ship x, y, angle, sprite_path, bullet_sprite_path, color
+ state.new_entity(:ship,
+ { x: x,
+ y: y,
+ dy: 0,
+ dx: 0,
+ damage: 0,
+ dead: false,
+ angle: angle,
+ max_alpha: 255,
+ sprite_path: sprite_path,
+ bullet_sprite_path: bullet_sprite_path,
+ color: color })
+ end
+
+ def new_red_ship
+ default_ship(400, 250.randomize(:sign, :ratio),
+ 180, 'sprites/ship_red.png', 'sprites/red_bullet.png',
+ [255, 90, 90])
+ end
+
+ def new_blue_ship
+ default_ship(-400, 250.randomize(:sign, :ratio),
+ 0, 'sprites/ship_blue.png', 'sprites/blue_bullet.png',
+ [110, 140, 255])
+ end
+
+ def render
+ render_instructions
+ render_score
+ render_universe
+ render_flames
+ render_ships
+ render_bullets
+ end
+
+ def render_ships
+ update_ship_outputs(state.ship_blue)
+ update_ship_outputs(state.ship_red)
+ outputs.sprites << [state.ship_blue.sprite, state.ship_red.sprite]
+ outputs.labels << [state.ship_blue.label, state.ship_red.label]
+ end
+
+ def render_instructions
+ return if state.ship_blue.dx > 0 || state.ship_blue.dy > 0 ||
+ state.ship_red.dx > 0 || state.ship_red.dy > 0 ||
+ state.flames.length > 0
+
+ outputs.labels << [grid.left.shift_right(30),
+ grid.bottom.shift_up(30),
+ "Two gamepads needed to play. R1 to accelerate. Left and right on D-PAD to turn ship. Hold A to shoot. Press B to drop mines.",
+ 0, 0, 255, 255, 255]
+ end
+
+ def calc
+ calc_thrusts
+ calc_ships
+ calc_bullets
+ calc_winner
+ end
+
+ def input
+ input_accelerate
+ input_turn
+ input_bullets_and_mines
+ end
+
+ def render_score
+ outputs.labels << [grid.left.shift_right(80),
+ grid.top.shift_down(40),
+ state.ship_blue_score, 30, 1, state.ship_blue.color]
+
+ outputs.labels << [grid.right.shift_left(80),
+ grid.top.shift_down(40),
+ state.ship_red_score, 30, 1, state.ship_red.color]
+ end
+
+ def render_universe
+ return if outputs.static_solids.any?
+ outputs.static_solids << grid.rect
+ outputs.static_solids << state.stars
+ end
+
+ def apply_round_finished_alpha entity
+ return entity unless state.round_finished_debounce
+ entity.a *= state.round_finished_debounce.percentage_of(2.seconds)
+ return entity
+ end
+
+ def update_ship_outputs ship, sprite_size = 66
+ ship.sprite =
+ apply_round_finished_alpha [sprite_size.to_square(ship.x, ship.y),
+ ship.sprite_path,
+ ship.angle,
+ ship.dead ? 0 : 255 * ship.created_at.ease(2.seconds)].sprite
+ ship.label =
+ apply_round_finished_alpha [ship.x,
+ ship.y + 100,
+ "." * 5.minus(ship.damage).greater(0), 20, 1, ship.color, 255].label
+ end
+
+ def render_flames sprite_size = 6
+ outputs.sprites << state.flames.map do |p|
+ apply_round_finished_alpha [sprite_size.to_square(p.x, p.y),
+ 'sprites/flame.png', 0,
+ p.max_alpha * p.created_at.ease(p.lifetime, :flip)].sprite
+ end
+ end
+
+ def render_bullets sprite_size = 10
+ outputs.sprites << state.bullets.map do |b|
+ apply_round_finished_alpha [b.sprite_size.to_square(b.x, b.y),
+ b.owner.bullet_sprite_path,
+ 0, b.max_alpha].sprite
+ end
+ end
+
+ def wrap_location! location
+ location.x = grid.left if location.x > grid.right
+ location.x = grid.right if location.x < grid.left
+ location.y = grid.top if location.y < grid.bottom
+ location.y = grid.bottom if location.y > grid.top
+ location
+ end
+
+ def calc_thrusts
+ state.flames =
+ state.flames
+ .reject(&:old?)
+ .map do |p|
+ p.speed *= 0.9
+ p.y += p.angle.vector_y(p.speed)
+ p.x += p.angle.vector_x(p.speed)
+ wrap_location! p
+ end
+ end
+
+ def all_ships
+ [state.ship_blue, state.ship_red]
+ end
+
+ def alive_ships
+ all_ships.reject { |s| s.dead }
+ end
+
+ def calc_bullet bullet
+ bullet.y += bullet.angle.vector_y(bullet.speed)
+ bullet.x += bullet.angle.vector_x(bullet.speed)
+ wrap_location! bullet
+ explode_bullet! bullet if bullet.old?
+ return if bullet.exploded
+ return if state.round_finished
+ alive_ships.each do |s|
+ if s != bullet.owner &&
+ s.sprite.intersect_rect?(bullet.sprite_size.to_square(bullet.x, bullet.y))
+ explode_bullet! bullet, 10, 5, 30
+ s.damage += 1
+ end
+ end
+ end
+
+ def calc_bullets
+ state.bullets.each { |b| calc_bullet b }
+ state.bullets.reject! { |b| b.exploded }
+ end
+
+ def create_explosion! type, entity, flame_count, max_speed, lifetime, max_alpha = 255
+ flame_count.times do
+ state.flames << state.new_entity(type,
+ { angle: 360.randomize(:ratio),
+ speed: max_speed.randomize(:ratio),
+ lifetime: lifetime,
+ x: entity.x,
+ y: entity.y,
+ max_alpha: max_alpha })
+ end
+ end
+
+ def explode_bullet! bullet, flame_override = 5, max_speed = 5, lifetime = 10
+ bullet.exploded = true
+ create_explosion! :bullet_explosion,
+ bullet,
+ flame_override,
+ max_speed,
+ lifetime,
+ bullet.max_alpha
+ end
+
+ def calc_ship ship
+ ship.x += ship.dx
+ ship.y += ship.dy
+ wrap_location! ship
+ end
+
+ def calc_ships
+ all_ships.each { |s| calc_ship s }
+ return if all_ships.any? { |s| s.dead }
+ return if state.round_finished
+ return unless state.ship_blue.sprite.intersect_rect?(state.ship_red.sprite)
+ state.ship_blue.damage = 5
+ state.ship_red.damage = 5
+ end
+
+ def create_thruster_flames! ship
+ state.flames << state.new_entity(:ship_thruster,
+ { angle: ship.angle + 180 + 60.randomize(:sign, :ratio),
+ speed: 5.randomize(:ratio),
+ max_alpha: 255 * ship.created_at_elapsed.percentage_of(2.seconds),
+ lifetime: 30,
+ x: ship.x - ship.angle.vector_x(40) + 5.randomize(:sign, :ratio),
+ y: ship.y - ship.angle.vector_y(40) + 5.randomize(:sign, :ratio) })
+ end
+
+ def input_accelerate_ship should_move_ship, ship
+ return if ship.dead
+
+ should_move_ship &&= (ship.dx + ship.dy).abs < 5
+
+ if should_move_ship
+ create_thruster_flames! ship
+ ship.dx += ship.angle.vector_x 0.050
+ ship.dy += ship.angle.vector_y 0.050
+ else
+ ship.dx *= 0.99
+ ship.dy *= 0.99
+ end
+ end
+
+ def input_accelerate
+ input_accelerate_ship inputs.controller_one.key_held.r1 || inputs.keyboard.up, state.ship_blue
+ input_accelerate_ship inputs.controller_two.key_held.r1, state.ship_red
+ end
+
+ def input_turn_ship direction, ship
+ ship.angle -= 3 * direction
+ end
+
+ def input_turn
+ input_turn_ship inputs.controller_one.left_right + inputs.keyboard.left_right, state.ship_blue
+ input_turn_ship inputs.controller_two.left_right, state.ship_red
+ end
+
+ def input_bullet create_bullet, ship
+ return unless create_bullet
+ return if ship.dead
+
+ state.bullets << state.new_entity(:ship_bullet,
+ { owner: ship,
+ angle: ship.angle,
+ max_alpha: 255 * ship.created_at_elapsed.percentage_of(2.seconds),
+ speed: 5 + ship.dx.mult(ship.angle.vector_x) + ship.dy.mult(ship.angle.vector_y),
+ lifetime: 120,
+ sprite_size: 10,
+ x: ship.x + ship.angle.vector_x * 32,
+ y: ship.y + ship.angle.vector_y * 32 })
+ end
+
+ def input_mine create_mine, ship
+ return unless create_mine
+ return if ship.dead
+
+ state.bullets << state.new_entity(:ship_bullet,
+ { owner: ship,
+ angle: 360.randomize(:sign, :ratio),
+ max_alpha: 255 * ship.created_at_elapsed.percentage_of(2.seconds),
+ speed: 0.02,
+ sprite_size: 10,
+ lifetime: 600,
+ x: ship.x + ship.angle.vector_x * -50,
+ y: ship.y + ship.angle.vector_y * -50 })
+ end
+
+ def input_bullets_and_mines
+ return if state.bullets.length > 100
+
+ [
+ [inputs.controller_one.key_held.a || inputs.keyboard.key_held.space,
+ inputs.controller_one.key_down.b || inputs.keyboard.key_down.down,
+ state.ship_blue],
+ [inputs.controller_two.key_held.a, inputs.controller_two.key_down.b, state.ship_red]
+ ].each do |a_held, b_down, ship|
+ input_bullet(a_held && state.tick_count.mod_zero?(10).or(a_held == 0), ship)
+ input_mine(b_down, ship)
+ end
+ end
+
+ def calc_kill_ships
+ alive_ships.find_all { |s| s.damage >= 5 }.each do |s|
+ s.dead = true
+ create_explosion! :ship_explosion, s, 20, 20, 30, s.max_alpha
+ end
+ end
+
+ def calc_score
+ return if state.round_finished
+ return if alive_ships.length > 1
+
+ if alive_ships.first == state.ship_red
+ state.ship_red_score += 1
+ elsif alive_ships.first == state.ship_blue
+ state.ship_blue_score += 1
+ end
+
+ state.round_finished = true
+ end
+
+ def calc_reset_ships
+ return unless state.round_finished
+ state.round_finished_debounce ||= 2.seconds
+ state.round_finished_debounce -= 1
+ return if state.round_finished_debounce > 0
+ start_new_round!
+ end
+
+ def start_new_round!
+ state.ship_blue = new_blue_ship
+ state.ship_red = new_red_ship
+ state.round_finished = false
+ state.round_finished_debounce = nil
+ state.flames.clear
+ state.bullets.clear
+ end
+
+ def calc_winner
+ calc_kill_ships
+ calc_score
+ calc_reset_ships
+ end
+end
+
+$dueling_spaceship = DuelingSpaceships.new
+
+def tick args
+ args.grid.origin_center!
+ $dueling_spaceship.inputs = args.inputs
+ $dueling_spaceship.outputs = args.outputs
+ $dueling_spaceship.state = args.state
+ $dueling_spaceship.grid = args.grid
+ $dueling_spaceship.tick
+end