ぷよぷよ19連鎖

ちょうど1年前 Ruby を覚えたての頃書いたコードを発見した。
Ruby をインストールした直後で、何か書いてみたくてネットで例題を探していた記憶がある。

問題はこれ。
http://okajima.air-nifty.com/b/2011/01/2011-ffac.html

 ゲーム「ぷよぷよ」で、フィールドの状態がテキストで与えられたとき、消える「ぷよ」を消して次のフィールドの状態を出力するプログラムを書け。

1行目からいきなり腰が抜けそうな書きかたで、他にも今ならこうは書かないだろうというところがあるけど、面白いからそのままにしておく。
19連鎖するだけなら、1時間弱で完成したと思う。

何が自分をそこまで駆り立てたのか分からないけど、何故かスコア計算までするようになっている。
今にしてみれば、もっと面白い問題がいくらでもあると思うんだけど。

DATA = "  GYRR|RYYGYG|GYGYRR|RYGYRG|YGYRYG|GYRYRG|YGYRYR|YGYRYR|YRRGRG|RYGYGG|GRYGYR|GRYGYR|GRYGYR"


class Scoring
  attr_reader :output

  def initialize()
    @point = @sum_group_bonus = 0
    @colors = []
  end

  def read(n_puyo, color)
    @point += 10 * n_puyo
    @colors << color
    @sum_group_bonus += group_bonus(n_puyo)
  end

  def calculate(chain)
    mul = multiplier(chain, @colors.uniq.size, @sum_group_bonus)
    @output = "chain = #{chain}\nscore = #{@point} * #{mul}"
    @point * mul
  end

  private

  def multiplier(chain, n_color, sum_group_bonus)
    mul = chain_bonus(chain) + color_bonus(n_color) + sum_group_bonus
    mul == 0 ? 1 : mul
  end

  def chain_bonus(chain)
    if chain <= 3 then
      8 * (chain - 1)
    else
      32 * (chain - 3)
    end
  end

  def color_bonus(n_color)
    if n_color <= 1 then
      0
    else
      3 * 2**(n_color - 2)
    end
  end

  def group_bonus(n_puyo)
    case n_puyo
    when 4 then
      0
    when 5..10 then
      n_puyo - 3
    else
      10
    end
  end
end


class Puyo
  def initialize
    @board = DATA.split("|").map {|s| s.split(//)}
    @width = @board[0].size
    @height = @board.size
    @chain = @total = 0
    put
  end

  def disappear
    @chain += 1
    disappeared = false
    @done = Array.new(@height) {Array.new(@width, false)}
    scoring = Scoring.new
    @width.times do |x|
      @height.times do |y|
        n_puyo = connect(x, y)
        if n_puyo >= 4 then
          disappeared = true
          scoring.read(n_puyo, @board[y][x])
          @queue.each {|pos| @board[pos[:y]][pos[:x]] = "*"}
        end
      end
    end
    return false unless disappeared
    @total += scoring.calculate(@chain)
    puts scoring.output
    put
    true
  end

  def fall
    @width.times do |x|
      yy = @height - 1
      (@height - 1).downto(0) do |y|
        color = @board[y][x]
        @board[y][x] = " "
        if color != "*" then
          @board[yy][x] = color
          yy -= 1
        end
      end
    end
    put
  end

  private

  def put
    puts "total = #{@total}\n"
    @height.times do |y|
      print "|", @board[y].join, "|"
      puts
    end
    puts " ------"
    puts
    sleep 1
  end

  def connect(x, y)
    color = @board[y][x]
    return 0 if color == " "
    @queue = []
    enqueue(x, y, color)
    n = 0
    while @queue[n] do
      xx, yy = @queue[n].values_at(:x, :y)
      enqueue(xx - 1, yy, color)
      enqueue(xx + 1, yy, color)
      enqueue(xx, yy - 1, color)
      enqueue(xx, yy + 1, color)
      n += 1
    end
    n
  end

  def enqueue(x, y, color)
    if (0...@width).member?(x) and (0...@height).member?(y) and
      @board[y][x] == color and !@done[y][x] then
      @queue <<= {:x => x, :y => y}
      @done[y][x] = true
    end
  end
end


puyo = Puyo.new
puyo.fall while puyo.disappear