Here's a revised version of Midnight 'Roids. It's got engine glow,
and asteroid explosions. And some cleaned-up code, so that it
tips the scales at 299 lines.

(left, right, up, space to play)
(N for a new game)

# Midnight 'Roids Rev. 2
# This code is hereby released into the public domain.

class Asteroids
  WIDTH = 700
  HEIGHT = 600
  
  def self.new_game
    @lives = 3
    @level = 0
    @text = nil
    @ship = Ship.new(WIDTH/2, HEIGHT/2)
    @roids_count = 3 
    self.start_new_level
  end
  
  def self.start_new_level
    @level += 1
    unsafe_to_start = true
    while unsafe_to_start do
      Roid.reset
      @roids_count.times {|i| Roid.new(rand(WIDTH), rand(HEIGHT), (rand - 0.5)*2.5, (rand - 0.5)*2.5, Roid::LARGE) }
      unsafe_to_start = Roid.check_for_collision_with(@ship, Ship::UNIT*5)
    end
  end
  
  def self.game_over
    @text = "Game Over"
  end
  
  def self.draw
    @ship.check_for_collision_with_roids if @ship
    Ray.check_for_collision_with_roids
    $app.clear do
      $app.background $app.black
      $app.para("Lives left: [EMAIL PROTECTED]", :top => 5, :left => 5, :stroke => $app.gray(0.5), :font => "13px")
      $app.para("Level: [EMAIL PROTECTED]", :top => 5, :left => WIDTH - 75, :stroke => $app.gray(0.5), :font => "13px")
      $app.title(@text, :top => 240, :left => 80, :stroke => $app.gray(0.1), :font => "bold 100px") if @text
      @ship.draw if @ship
      Ray.draw_all
      Roid.draw_all
      Explosion.draw_all
    end
  end
  
  def self.ship_explodes
    center_x, center_y = @ship.center_x, @ship.center_y
    if @lives > 0
      @lives -= 1
      unsafe_to_start = true
      @ship = Ship.new(WIDTH/2, HEIGHT/2)
      while unsafe_to_start do
        unsafe_to_start = Roid.check_for_collision_with(@ship, Ship::UNIT*5)
        @ship = Ship.new(rand(WIDTH), rand(HEIGHT)) if unsafe_to_start
      end
    else
      @ship = nil
      self.game_over
    end
    50.times do |i|
      direction = ((2 * Math::PI) * i/50)
      Ray.new(center_x, center_y, 10*Math.cos(direction), 10*Math.sin(direction), direction)
    end
  end
  
  def self.maybe_start_new_level
    if Roid.roids_left == 0
      @roids_count += 1
      self.start_new_level
    end
  end
  
  def self.keypress(key)
    @ship.move(key) if [:left, :right, :up].include?(key) && @ship
    @ship.shoot if (key == ' ') && @ship
    self.new_game if key == 'n'
  end
end

class CelestialBody
  attr_accessor :x, :y, :size
  
  def center_x
    @x + @size/2
  end
  
  def center_y
    @y + @size/2
  end
  
  def initialize(x, y, vel_x, vel_y, size)
    @x, @y, @vel_x, @vel_y, @size = x, y, vel_x, vel_y, size
  end
  
  def intersects_with?(thing, margin = 0)
    return true if Math.sqrt((center_x - thing.center_x)**2 + (center_y - thing.center_y)**2) < (@size/2 + ((thing.size + margin) / 2))
    return false
  end
end

class Roid < CelestialBody
  LARGE = 80
  MEDIUM = 45
  SMALL = 25
  
  def self.add_roid(roid)
    @roids << roid
  end
  
  def self.remove_roid(roid)
    @roids.delete_at(roid)
  end
  
  def self.draw_all
    @roids.each {|roid| roid.draw }
  end
  
  def self.roids_left
    @roids.length
  end
  
  def self.reset
    @roids = []
  end
  
  def initialize(x, y, vel_x, vel_y, size)
    super(x, y, vel_x + (rand - 0.5)*2.5, vel_y + (rand - 0.5)*2.5, size)
    @color = $app.rgb((rand-0.5)*0.2 + 0.15, (rand-0.5)*0.2 + 0.15, (rand-0.5)*0.3+ 0.45, 0.5)
    Roid.add_roid(self)
  end
  
  def draw
    @x += @vel_x; @y += @vel_y
    @x = @x % Asteroids::WIDTH; @y = @y % Asteroids::HEIGHT
    $app.stroke($app.gray(1.0, 0.5))
    $app.fill(@color)
    $app.oval(@x, @y, @size, @size)
  end
  
  def explode(index)
    Roid.remove_roid(index)
    explode_factor = 0
    case @size
    when LARGE
      explode_factor = rand(10) + 15
      2.times { Roid.new(center_x - MEDIUM/2, center_y - MEDIUM/2, @vel_x, @vel_y, MEDIUM) }
    when MEDIUM
      explode_factor = rand(5) + 5
      2.times { Roid.new(center_x - SMALL/2, center_y - SMALL/2, @vel_x, @vel_y, SMALL) }
    when SMALL
      explode_factor = rand(3) + 2
      Asteroids.maybe_start_new_level
    end
    explode_factor.times do |i|
      direction = ((2 * Math::PI) * i/explode_factor)
      Explosion.new(center_x, center_y, 4*Math.cos(direction), 4*Math.sin(direction))
    end
  end
  
  def self.check_for_collision_with(thing, margin = 0)
    @roids.each_with_index do |roid, index|
      return [roid, index] if roid.intersects_with?(thing, margin)
    end
    return false
  end
end

class Explosion < CelestialBody
  @explosions = []
  def self.add_explosion(explosion)
    @explosions << explosion
  end
  
  def self.remove(index)
    @explosions.delete_at(index)
  end
  
  def self.draw_all
    @explosions.each_with_index {|explosion, i| explosion.draw(i) }
  end
  
  def initialize(x, y, vel_x, vel_y)
    super( x, y, vel_x + (rand-0.5)*4, vel_y + (rand-0.5)*4, 10)
    @transparency = 1.0
    Explosion.add_explosion(self)
  end
  
  def draw(index)
    @x += @vel_x; @y += @vel_y
    $app.stroke($app.gray(1.0, @transparency))
    color = [(rand-0.5)*0.7 + 0.7, (rand-0.5)*0.7 + 0.7, (rand-0.5)*0.1 + 0.3, @transparency]
    $app.fill($app.rgb(*color))
    $app.oval(@x, @y, @size, @size)
    @transparency -= 0.065
    Explosion.remove(index) if @transparency <= 0
  end
end

class Ship < CelestialBody
  UNIT = 30
  
  def initialize(x, y)
    super(x, y, 0, 0, UNIT)
    @direction = 0
    @glow = 0
  end
  
  def draw
    @x += @vel_x; @y += @vel_y
    @x = @x % Asteroids::WIDTH; @y = @y % Asteroids::HEIGHT
    $app.stroke($app.gray(1.0, 0.5))
    $app.fill($app.rgb(0.7, 0.2, 0.2, 0.5))
    $app.oval(@x, @y, UNIT, UNIT)
    $app.oval(@x + 20*Math.cos(@direction) + 10, @y + 20*Math.sin(@direction) + 10, UNIT/3, UNIT/3)
    $app.fill($app.rgb(1.0, 1.0, 0.8, 0.5))
    $app.oval(@x + 5*Math.cos(@direction) + 12.5, @y + 5*Math.sin(@direction) + 12.5, UNIT/5, UNIT/5)
    if @glow > 0
      $app.stroke($app.gray(1.0, @glow + 0.2))
      $app.fill($app.gray(1.0, @glow))
      $app.oval(@x + 15*Math.cos(@direction - Math::PI + 0.35) + 11, @y + 15*Math.sin(@direction - Math::PI + 0.35) + 11, UNIT/4, UNIT/4)
      $app.oval(@x + 15*Math.cos(@direction - Math::PI - 0.35) + 11, @y + 15*Math.sin(@direction - Math::PI - 0.35) + 11, UNIT/4, UNIT/4)
      @glow -= 0.06
    end
  end
  
  def shoot
    Ray.new(@x + UNIT/2, @y + UNIT/2, @vel_x, @vel_y, @direction)
  end
  
  def move(way)
    case way
    when :left
      @direction -= 0.25
    when :right
      @direction += 0.25
    when :up
      @glow = 0.8
      @vel_x += 1.5*Math.cos(@direction); @vel_y += 1.5*Math.sin(@direction)
      ["@vel_x", "@vel_y"].each do |vel|
        instance_variable_set(vel, 10) if instance_variable_get(vel) > 10
        instance_variable_set(vel, -10) if instance_variable_get(vel) < -10
      end
    end
  end
  
  def check_for_collision_with_roids
    if roid = Roid.check_for_collision_with(self)
      Asteroids.ship_explodes
    end
  end
end

class Ray < CelestialBody
  @rays = []
  
  def self.add_ray(ray)
    @rays << ray
  end
  
  def self.draw_all
    @rays.each_with_index {|ray, i| ray.draw(i) }
  end
  
  def self.remove_ray(index)
    @rays.delete_at(index)
  end
  
  def self.check_for_collision_with_roids
    @rays.each_with_index do |ray, index| 
      if roid_list = Roid.check_for_collision_with(ray)
        Ray.remove_ray(index)
        roid_list[0].explode(roid_list[1])
      end
    end
  end
  
  def initialize(x, y, vel_x, vel_y, direction)
    @direction = direction
    super(x, y, vel_x + 8 * Math.cos(@direction), vel_y + 8 * Math.sin(@direction), 5)
    Ray.add_ray(self)
  end
  
  def draw(index)
    @x += @vel_x; @y += @vel_y
    if @x > Asteroids::WIDTH || @x < 0 || @y > Asteroids::HEIGHT || @y < 0
      Ray.remove_ray(index)
    else
      $app.stroke($app.rgb(1.0, 1.0, 0.8, 0.8))
      $app.line(@x, @y, @x + @vel_x, @y + @vel_y)
    end
  end
end

Shoes.app :width => Asteroids::WIDTH, :height => Asteroids::HEIGHT, :title => "Asteroids", :resizable => false do
  $app = self
  Asteroids.new_game
  animate(60) { Asteroids.draw }
  keypress {|k| Asteroids.keypress(k) }
end



That's all,
— Jeremy


Reply via email to