[[目次へもどる>PuyoPuyo]]
* ジャマーを落とそう [#f25c0348]
#contents

ここまでできたらそろそろ得点計算を考えたくなる頃である.
しかし得点計算のために一つ重要なものを忘れてはいないだろうか?

そう,おじゃまぷよである.我々はおじゃまぷよをジャマーと呼ぶことにしよう.
別に邪魔だからジャマーと呼ぶわけではなく英語でjammerである.

** JammerManager [#k0149d43]
では早速ジャマーを管理するJammerManagerクラスを作ろう.
#sh(ruby){{
class JammerManager
  attr_accessor :jammers
  def initialize(row_s, line_s)
    @row_s = row_s
    @line_s = line_s
    @jammers = 0
    @fall_max = (line_s / 3 + 1) * row_s
    init_order
  end

  def init_order
    @order = Array.new(@row_s){|r| r}.shuffle!.rotate_each
  end

  def jammers=(num)
    @jammers = num
    @jammers = 0 if @jammers < 0
  end

  def get_fall_table
    fall_num = @jammers > @fall_max ? @fall_max : @jammers
    @jammers -= fall_num
    # make table
    table = Array.new(@row_s){ [] }
    @row_s.times do
      break if fall_num == 0 
      row = @order.next
      fall_num -= 1
      num = 1 + fall_num / @row_s
      table[row] = Array.new(num, :j)
    end
    return table
  end
end

}}
初期化ではフィールドの大きさを与える.これは一度に何個のジャマーを落とすか計算するためである.

init_orderメソッドではジャマーがどの列にどの順で落ちるかを決定している.

get_fall_tableメソッドでは落下するジャマーのテーブルを返す.
ジャマーが無ければ空のテーブルだし,めっちゃあれば一度に落下するのは最大6*5=30個までとなるようなテーブルを返す.

ちなみにBlockクラスに新たにジャマーを表すカラー:jを追加している.

** ジャマーを落とそう [#scb26b8d]
まずジャマーマネージャーを初期化する.ジャマーはフィールドごとに別に持つと考えられる((フィーバーモードではおじゃまぷよは別に管理される))ので,フィールドクラスにジャマーマネージャーを保持してもらう.
#sh(ruby){{
class Field
  def initialize(row_s, line_s, block_s)
    @row_s = row_s
    @line_s = line_s
    @block_s = block_s
    @fallen = false
    @eliminated = false
    init_control_block_manager
    init_table
    init_jammer_manager
    init_blocklist
    init_connect_table
  end
  def init_jammer_manager
    @jm = JammerManager.new(@row_s, @line_s)
  end
  def falldown_line(r,speed,waiting)
    (略)
  end
  def falldown(speed = -6, waiting = true)
    @fallen = false
    @row_s.times do |r|
      falldown_line(r,speed,waiting)
    end
    @fallen
  end
  def start_fall_jammer
    fall_table = @jm.get_fall_table
    fall_table.each.with_index do |jammers, row|
      # col => block
      jammers = jammers.map.with_index{|col, i|
        next unless col
        block = StableBlock.new(col, @block_s)
        block.row = row
        block.line = @line_s + 2 + i # base : linesize + 2[hide block]
        @active_blocks.push block
        block
      }
      # extend row to size of @line_s + 2
      @table[row][@line_s + 1] = @table[row][@line_s + 1]
      # concat
      @table[row].concat jammers
    end
  end
  def eliminate_connection
    @connect_table.each do |connection|
      next if connection.size < 4
      @eliminated = true # check flag
      #### test jammer ####
      @jm.jammers += connection.size
      connection.each do |block|
        block.set_collapse(40)
        @table[block.row][block.line] = nil
        @active_blocks.delete block
        @collapse_blocks.push block
      end
    end
  end
end
}}

start_fall_jammerメソッドによってジャマーをフィールドにセットする.
まずはジャマーマネージャーからジャマーテーブルを取得し,それに従ってブロックを生成する.この時,ジャマーが画面上から落下してくるため,まずはフィールドを高さ+2まで拡張する(フィールドの上見えないところ二個分までブロックを積めるため).その後,作ったジャマーテーブルを丸ごとくっつけることで,フィールドの上空にジャマーが配置される.

また,ジャマーの落下は一度にドスン!と落ちてきた方がそれっぽいしストレスもたまらないので,falldown_lineメソッドを拡張して落下速度と落下アニメーションの拡張で行った列ごとにばらばら落下させるかどうかを変更できるようにした.

そして肝心のジャマーの追加だが,現状では対戦相手がおらずジャマーの発生のしようがないので,自分が連鎖をするとその分だけジャマーが降るという理不尽な仕様にした.

あとはジャマーを落下させるフェーズを追加すれば完成である.
#sh(ruby){{
class FieldController
  def init_phase
    @phase = Phase.new
    # added :control_block handler
    @phase.add_start_handler(:control_block,
                             method(:start_control_block))
    # added :falldown handler
    @phase.add_condition_handler(:falldown,
                                 method(:falldown_cond))
    # added :elimiate handler
    @phase.add_condition_handler(:eliminate,
                                 method(:eliminate_cond))
    # added :fall_jammer handler
    @phase.add_start_handler(:fall_jammer,
                         method(:start_fall_jammer))
    @phase.add_condition_handler(:fall_jammer,
                                 method(:fall_jammer_cond))
    @phase.add_end_handler(:fall_jammer,
                           method(:end_fall_jammer_to_control_block))
    
    @phase.change :control_block
  end
  def update
    #### debug ####
    @field.instance_eval{Debug.print @jm.jammers}
    ###############
    update_blocks
    draw_field
    return if @phase.waiting
    case @phase.phase
    when :phase_trans
      @phase.trans_condition_check
    when :control_block
      update_control_block
    when :falldown
      update_falldown
    when :eliminate
      update_eliminate
    when :fall_jammer
      update_fall_jammer
    end
  end
  def start_fall_jammer
    @field.start_fall_jammer
  end
  def end_fall_jammer_to_control_block
    @phase.set_timer(16)
  end
  def update_eliminate
    eliminated = @field.eliminate
    @phase.change eliminated ? :falldown : :fall_jammer
  end
  def update_fall_jammer
    fallen = @field.falldown(-10, false)
    @phase.change :control_block
  end
  def eliminate_cond
    # wait collapse animation
    !@field.blocks_reasonable_collapse?
  end
  def fall_jammer_cond
    # wait fall && land animation && standard wait
    !@field.blocks_move? && !@field.blocks_land? && @phase.pred_timer
  end
end
}}
これは問題無いだろう.ジャマーフェーズに入ったらstart_fall_jammerを呼んでジャマーをセットし,落下速度などを指定した上で落下させるだけである.

*** 実行 [#y6eda6d8]
さあ早速完成したジャマーを見てみよう!ちなみに左上の数字は蓄積されたジャマーの数である.
#media(PuyoPuyoChap25/PuyoPuyoChap25.flv);
これでうまく行くと思った?残念!まじで残念な結果にwww~
ジャマーをセットして落下させただけなので4つそろうと消えてしまうし,ブロックを消したときに隣接したジャマーも消えるようになっていない.
それどころか消えたジャマーの分もそのままジャマーになってまた降ってくる!!悪夢!

** ジャマーをちゃんと処理しよう [#f4919cf3]
このままでは意味不明なのでジャマーをしっかり処理しよう.
まずジャマーは4つくっついても消えないため,つながりチェック時のチェックリストにも入れたくない.
そこで,ジャマーブロック一覧をその他のブロックと分けて扱うことにする.
#sh(ruby){{
class Field
  def init_blocklist
    @active_blocks = []
    @jamming_blocks = []
    @collapse_blocks = []
  end
  def start_fall_jammer
    fall_table = @jm.get_fall_table
    fall_table.each.with_index do |jammers, row|
      # col => block
      jammers = jammers.map.with_index{|col, i|
        next unless col
        block = StableBlock.new(col, @block_s)
        block.row = row
        block.line = @line_s + 2 + i # base : linesize + 2[hide block]
        @jamming_blocks.push block
        block
      }
      # extend row to size of @line_s + 2
      @table[row][@line_s + 1] = @table[row][@line_s + 1]
      # concat
      @table[row].concat jammers
    end
  end
end
}}

次につながりチェック時のデータ構造を拡張する.
今まではつながっているブロック一覧の配列を生成していたが,つながっているブロック一覧とそれに隣接するジャマー一覧のペアの配列を生成するようにする.
#sh(ruby){{
class Field
  def check_con_jammer(r,l,col,block)
    #return false unless @jamming_blocks.include? block # should be included
    @connect_table[-1][:jammers].push block
    return false
  end
  def check_con_block(r,l,col,block)
    return false unless @checklist.include? block
    return false unless @table[r][l] && @table[r][l].color == col
    @checklist.delete block
    @connect_table[-1][:blocks].push block
    return true
  end
  def check_connection(r,l,col)
    return if r < 0 || r >= @row_s
    return if l < 0 || l >= @line_s
    return unless @table[r][l]
    block = @table[r][l]
    cont = block.color == :j ? check_con_jammer(r,l,col,block) :
      check_con_block(r,l,col,block)
    return unless cont
    check_connection(r,l+1,col) # up
    check_connection(r,l-1,col) # down
    check_connection(r+1,l,col) # right
    check_connection(r-1,l,col) # left
  end
  def make_connect_table
    init_connect_table
    @checklist = @active_blocks.dup
    while !@checklist.empty?
      block = @checklist.first
      r = block.row; l = block.line
      # not consider if block out of screen
      if r < 0 || r >= @row_s || l < 0 || l >= @line_s
        @checklist.shift
        next
      end
      col = block.color
      @connect_table.push({:blocks => [], :jammers => []})
      check_connection(r,l,col)
    end
  end
end
}}
今調査しているブロックがジャマーだった時は現在のつながりに隣接するジャマーなので無条件にコネクションに追加し,再帰調査を終わる.それ以外の時は今までの処理と全く同じである.

これで得られたコネクションテーブルに基づいてブロックを消す.
これは単純で,4個以上つながっているコネクションであればそれに隣接するジャマーも消してしまえばいい.
少々注意なのはすでに他のコネクションにより消えているジャマーの可能性もあるので,アクティブなジャマーかどうかのチェックをする必要がある.
#sh(ruby){{
class Field
  def eliminate_connection
    @connect_table.each do |connection|
      next if connection[:blocks].size < 4
      @eliminated = true # check flag
      #### test jammer ####
      @jm.jammers += connection[:blocks].size
      # delete blocks
      connection[:blocks].each do |block|
        block.set_collapse(40)
        @table[block.row][block.line] = nil
        @active_blocks.delete block
        @collapse_blocks.push block
      end
      # delete jammers [naive]
      connection[:jammers].each do |jammer|
        next unless @jamming_blocks.include?(jammer) # already be deleted
        jammer.set_collapse(40)
        @table[jammer.row][jammer.line] = nil
        @jamming_blocks.delete jammer
        @collapse_blocks.push jammer
      end
    end
  end
end
}}

*** 実行 [#z691de2b]
今度はちゃんとしたジャマーとなっている.
#media(PuyoPuyoChap25/PuyoPuyoChap25_2.flv);

** 最後に少々 [#naf825f8]
ジャマーの挙動はほぼオーケーとなったが,ここで一つ見落としがちな事がある.
例えばフィールド一杯にブロックがあるときにジャマーが大量に降ったとする.
すると,フィールドの見えないところにジャマーが大量に乗っかっていることになる.
別にそれでもいいちゃあいいが,無制限にフィールドの高さが増えるとバグの温床になるため,ジャマーを降らせたら,フィールドの高さ+2より上にあるブロックは全て消してしまう事にする((実際のぷよぷよでもそうなっている)).

#sh(ruby){{
class Field
  def slice_limited_line
    limit = @line_s + 2
    @row_s.times do |row|
      next if @table[row].size < limit
      @table[row][limit..-1].each do |block|
        @jamming_blocks.delete block
      end
      @table[row] = @table[row][0...limit]
    end
  end
end

class FieldController
  def end_fall_jammer_to_control_block
    @phase.set_timer(16)
    # delete blocks is over limited line
    @field.slice_limited_line
  end
end
}}


トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS