[[目次へもどる>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 }}