# SHBX Tower (after NS Tower)
# Version Three.
# omygawshkenas!
# This code is hereby released into the public domain.

class Tower
  WIDTH = 650
  HEIGHT = 700
  BRICK = 25
  
  def self.draw
    $app.clear do
      $app.background($app.black)
      Brick.draw
      Platform.draw
      @hero.draw
    end
  end
  
  def self.new_game
    Brick.setup_tower_walls
    @hero = Hero.new(50, 545, $app.gray(255, 50), $app.gray(255, 255))
    Platform.setup_platforms
  end
  
  def self.keypress(key)
    @hero.jump(key)
  end
end

class Drawable
  
  def self.inherited(subclass)
    subclass.class_eval do
      @instances = []
      
      def self.draw
        self.each {|instance| instance.draw }
      end
      
      def self.each
        @instances.each {|instance| yield instance }
      end

      def self.add_instance(instance)
        @instances << instance
      end
    end
  end
  
  def initialize(x, y, fill, stroke)
    @x, @y = x, y
    @fill, @stroke = fill, stroke
    self.class.add_instance(self)
  end
  
  def draw
    $app.stroke(@stroke)
    $app.fill(@fill)
  end
  
end


class Hero < Drawable
  attr_accessor :platform
  attr_reader :x, :y, :radius
  R_LIMIT = Tower::WIDTH - Tower::BRICK
  L_LIMIT = Tower::BRICK
  GRAVITY = 0.8
  DEFAULT_CHARGE = 10
  SCROLL_LIMIT = Tower::HEIGHT / 3
  
  def self.hero
    @@hero
  end
  
  def initialize(x, y, fill, stroke)
    super(x, y, fill, stroke)
    @speed = 5
    @y_vel = 0
    @radius = 30
    @charge = DEFAULT_CHARGE
    @@hero = self
  end
  
  def foot
    [@x + 12, @y + 29, 5, 1]
  end
  
  def draw
    super
    self.run
    self.fall
    self.maybe_charge
    $app.oval(@x, @y, @radius, @radius)
    $app.fill($app.white)
    $app.rect(*self.foot)
  end
  
  def run
    @x += @speed
    @speed = -(@speed) if (@x + @radius) > R_LIMIT || @x < L_LIMIT  # Run back and forth
  end
  
  def charge
    @charging = true
  end
  
  def maybe_charge
    @charge += 1.5 if @charging && @y_vel == 0
    @charge = DEFAULT_CHARGE if @charge > 40
    self.colorize
  end
  
  def colorize
    @fill = $app.gray(255, @charge * (1.0 / 40))
  end
  
  def jump
    @charging = false
    if @y_vel == 0
      @y_vel = -(@charge * 0.65)
      @y_vel -= (30 * ((Math.cos(self.platform.bouncy) + 1) / 2)) if self.platform && self.platform.bouncy
    end
    @charge = DEFAULT_CHARGE
    self.colorize
  end
  
  def fall
    @y_vel += GRAVITY
    @y += @y_vel
    check = self.foot << self
    if ( @y_vel > 0 && collision = Platform.check_for_collision(*check))
      @y = collision - @radius
      @y_vel = 0
    elsif @y_vel < 0
      if @y < SCROLL_LIMIT
        Platform.scroll_up(SCROLL_LIMIT - @y)
        @y = SCROLL_LIMIT
      end
    end
    
    Tower.new_game if @y > Tower::HEIGHT
  end
    
end


class Platform < Drawable
  @offset = 0
  attr_accessor :width, :height, :bouncy
  attr_accessor :x, :y
  HEIGHT = Tower::BRICK
  WIDTH = 175
  @@previous_x = (0..0)
  
  def self.check_for_collision(x, y, width, height, hero=nil)
    self.each do |platform|
      result = platform.overlaps?(x, y, width, height, hero)
      return result if result
    end
    return false
  end
  
  def self.maybe_new
    fill = $app.rgb(255, 100, 100, 50)
    stroke = $app.gray(255,155)
    x = rand(Tower::WIDTH - WIDTH - (2 * Tower::BRICK)) + Tower::BRICK
    y = -(Tower::BRICK)
    if rand < 0.3
      unless Platform.check_for_collision(x - 30, y - 30, WIDTH + 60, Tower::BRICK + 60)
        unless @@previous_x.include?(x)
          Platform.new(x, y, fill, stroke, WIDTH)
          @@previous_x = ((x-20)..((x+20)))
        end
      end
    end
  end
  
  def self.setup_platforms
    60.times do
      self.scroll_up(10)
    end
    Platform.new(Tower::BRICK, Tower::HEIGHT-Tower::BRICK, $app.rgb(200,10,10,100), $app.gray(255,100), Tower::WIDTH - Tower::BRICK * 2, 800)
  end
  
  def self.remove(instance)
    @instances.delete(instance)
  end
  
  def self.scroll_up(pixels)
    self.maybe_new
    self.each do |platform|
      platform.y = platform.y + pixels
      Platform.remove(platform) if platform.y > Tower::HEIGHT
    end
  end
  
  def initialize(x, y, fill, stroke, width, height=HEIGHT)
    super(x, y, fill, stroke)
    self.width = width
    self.height = height
    self.bouncy = false
    self.bouncy = 100 if rand < 0.08
  end
  
  def draw
    if self.bouncy
      @bouncy += 0.05
      @fill = $app.rgb(200,10,10,((Math.cos(@bouncy) + 1) / 2))
    end
    super
    $app.rect(@x, @y, @width, @height)
  end
  
  def overlaps?(x, y, width, height, hero=nil)
    return false if (y + height) < @y
    return false if y > @y + @height
    return false if x + width < @x
    return false if x > @x + @width
    hero.platform = self if hero
    return @y
  end
  
end


class Brick < Drawable
  
  def draw
    super
    $app.rect(@x, @y, Tower::BRICK, Tower::HEIGHT)
  end
  
  def self.setup_tower_walls
    @instances = []
    stroke = $app.gray(255, 100)
    color = $app.rgb(50, rand(20) + 70, rand(50)+150, rand(50)+80)
    Brick.new(0, 0, color, stroke)
    Brick.new(Tower::WIDTH - Tower::BRICK, 0, color, stroke)
  end
end


Shoes.app :width => Tower::WIDTH, :height => Tower::HEIGHT, :title => "Tower", :resizeable => false do
  $app = self
  Tower.new_game
  animate(40) { Tower.draw }
  click { Hero.hero.charge }
  release { Hero.hero.jump }
  keypress {|key| Tower.new_game if key.downcase == 'n'}
end