# This sample need ruby 1.8 or 1.6 with shim
# Thanks to Simon Strandgaard
require 'sdl'
if RUBY_VERSION < "1.7" then
  require 'features/ruby18'
end

class Object
  def deep_clone
    Marshal::load(Marshal.dump(self))
  end
end

class Array
  def random
    at(Kernel.rand(size))
  end
end

class Pattern
  def initialize(x, y, *data)
    @x = x
    @y = y
    @data = data
  end
  attr_reader :data, :x, :y
  alias width x
  def rotate
    @data = @data.reverse.transpose
    @x, @y = @y, @x
  end
  PAT1 = Pattern.new(4, 1, [1, 1, 1, 1])
  PAT2 = Pattern.new(3, 2, [1, 1, 1], [0, 1, 0])
  PAT3 = Pattern.new(3, 2, [1, 1, 1], [1, 0, 0])
  PAT4 = Pattern.new(3, 2, [1, 1, 1], [0, 0, 1])
  PAT5 = Pattern.new(2, 2, [1, 1], [1, 1])
  PAT6 = Pattern.new(3, 2, [1, 1, 0], [0, 1, 1])
  PAT7 = Pattern.new(3, 2, [0, 1, 1], [1, 1, 0])
  def Pattern::patterns
    [PAT1, PAT2, PAT3, PAT4, PAT5, PAT6, PAT7]
  end
end

class Level
  def initialize(width=10, height=15)
    @height = height
    @width = width
    @data = Array.new(height) { Array.new(width, 0) }
    backup_background
  end
  attr_reader :width, :height
  def test
    @data[0][0] = 1
    @data[0][@width-1] = 1
    @data[@height-1][0] = 1
    @data[@height-1][@width-1] = 1
  end
  def bg_is_collision(offset_x, offset_y, pattern)
    return true if pattern.x + offset_x > @width
    return true if pattern.y + offset_y > @height
    y = offset_y
    pattern.data.each do |row|
      x = offset_x
      row.each do |cell|
	if (cell != 0) and (@bg[y][x] != 0)
	  return true
	end
	x += 1
      end
      y += 1
    end
    return false
  end
  def restore_background
    @data = @bg
  end
  def backup_background
    @bg = @data.deep_clone
  end
  def or_pattern(offset_x, offset_y, pattern)
    backup_background
    y = offset_y
    pattern.data.each do |row|
      x = offset_x
      row.each do |cell|
	@data[y][x] = cell if cell != 0
	x += 1
      end
      y += 1
    end
  end
  def find_filled_rows 
    res = []
    @data.each_index do |y|
      ok = true
      @data[y].each do |cell|
	if cell == 0
	  ok = false 
	  break
	end
      end
      res << y if ok
    end
    res
  end
  def remove_rows(*rows)
    rows.uniq!
    rows.each do |y|
      @bg[y] = nil
    end
    @bg.compact!
    extra = Array.new(rows.size) { Array.new(@width, 0) }
    @bg = extra + @bg
    raise "integrity error" if @bg.size != @height
  end
end

class Level
  def load_render_data
    @image = SDL::Surface.load_bmp("icon.bmp")
    @image.set_color_key( SDL::SRCCOLORKEY ,0)
    @image = @image.display_format
    @step_x = 32  # todo: image width
    @step_y = 32  # todo: image height
    # todo: raise exception is level does not fit to screen!
    @offset_x = 100
    @offset_y = 20
    
    @fill_removal_dir = false
  end
  def render
    y = @offset_y
    @data.each do |row|
      render_line(y, row)
      y += @step_y
    end
  end
  def render_line(y, cells)
    x = @offset_x
    cells.each do |cell|
      i = (cell == 0) ? 63 : 255
      @image.set_alpha(SDL::SRCALPHA,i)
      $screen.put(@image,x,y)
      x += @step_x
    end
  end
  FILL = (20 * 256*256) + (0 * 256) + 10
  def render_removal(rows)
    $screen.fill_rect(0,0,640,512,0)
    render
    rows.reverse_each do |row|
      y = (row * @step_y) + @offset_y
      
      if @fill_removal_dir
	i = 0
	x = @offset_x
	while i < @width
	  $screen.fill_rect(x,y,@step_x,@step_y,FILL)
	  $screen.flip
	  i += 1
	  x += @step_x
	end
	@fill_removal_dir = false
      else
	i = 0
	x = @offset_x + ((@width-1)*@step_x)
	while i < @width
	  $screen.fill_rect(x,y,@step_x,@step_y,FILL)
	  $screen.flip
	  i += 1
	  x -= @step_x
	end
	@fill_removal_dir = true
      end
    end
  end
end

SDL.init( SDL::INIT_VIDEO )
$screen = SDL::Screen.open(640,512,24,SDL::SWSURFACE)
level = Level.new
level.test
level.load_render_data
pat = Pattern::PAT3.clone
pat_x = 5
pat_y = 0
time_step = 0.5
time = Time.now
launch_new_pattern = true
loop do
  if launch_new_pattern
    launch_new_pattern = false
    time_step = 0.5
    time = Time.now
    pat = Pattern::patterns.random.clone
    pat_x = 5
    pat_y = 0
    level.backup_background
    if level.bg_is_collision(pat_x, pat_y, pat)
      puts "Sorry you are game over"
      sleep 3
      raise "game over"
    end
    level.or_pattern(pat_x, pat_y, pat)
  end
  
  # timer events
  while Time.now > time + time_step
    pat_y += 1
    if level.bg_is_collision(pat_x, pat_y, pat)
      launch_new_pattern = true
      rows = level.find_filled_rows
      if rows.size > 0
	puts "rows filled"
	p rows
	level.render_removal(rows)
	
	level.backup_background
	level.remove_rows(*rows)
	level.restore_background
      end
    else
      level.restore_background
      level.or_pattern(pat_x, pat_y, pat)
    end
    time += time_step
  end
  
  old_pat_x = pat_x
  old_pat_y = pat_y
  rotate = false
  
  # handle keystrokes
  while event = SDL::Event.poll
    case event
    when SDL::Event::Quit then exit
    when SDL::Event::KeyDown
      case event.sym
      when SDL::Key::ESCAPE then exit 
      when SDL::Key::UP     then rotate = true
      when SDL::Key::DOWN   then time_step = 0.1
      when SDL::Key::LEFT
	pat_x -= 1 if pat_x > 0
      when SDL::Key::RIGHT
	pat_x += 1 if pat_x < (level.width - pat.width)
      end
    end
  end
  SDL::Key.scan
  
  # move pattern & do collition tests
  if (pat_x <=> old_pat_x) or (pat_y <=> old_pat_y) or (rotate == true)
    
    old_pat = pat.clone
    pat.rotate if rotate
    
    # if collision then restore last working-state
    if level.bg_is_collision(pat_x, pat_y, pat)
      pat_x = old_pat_x
      pat_y = old_pat_y
      pat = old_pat
    else
      # collision avoided.. therefore don't launch new pattern
      launch_new_pattern = false
      level.restore_background
      level.or_pattern(pat_x, pat_y, pat)
    end
  end
  
  # repaint screen
  $screen.fill_rect(0,0,640,512,0)
  level.render
  $screen.flip
end
