[[目次へもどる>PuyoPuyo]] * 対戦しよう [#d133e914] #contents ゲームモードが作れるようになったので早速新しいゲームモードを作ろう. ここはやはりプレイヤー同士の対戦が作りたいところだ. 幸い2プレイヤーのためのフィールドコントローラーはすでに作っておいたので((Player1,Player2クラス)), 今回作るべきはフィールドコントローラー間のやり取りである. ** 新しいゲームモード [#y0331ef0] まずは新しいゲームモードのためのシーンを作ってしまおう. SceneTwoPlayer((SceneTwoPlayersにするべきだった))は二人のプレイヤーが 対戦するゲームモードである. とはいっても多人数プレイを意識した設計になっているので特に気を張る必要はない. ひとつシングルモードと異なるのはrivalsだろう. ここに敵となるフィールドコントローラーの配列をセットすることで, 自動的にジャマーが相手に発生するようになる予定だ. #sh(ruby){{ class SceneTwoPlayer < ScenePuyoPuyo def start super player1 = Player1.new(16,16,6,12,16) player2 = Player2.new(32 + 16 *6,16,6,12,16) player1.rivals = [player2] player2.rivals = [player1] @players.push(player1) @players.push(player2) end def gameover GameMain.scene_change SceneTwoPlayer end end }} ** ジャマーの発生と相殺 [#r812e95c] フィールドコントローラーでセットするライバルの配列は フィールドクラスを経てジャマーマネージャーの@rivalsにセットされる. このとき登録される配列の中身はライバルのジャマーマネージャーに変換される. ここで再びジャマーに関する考察を行おう. まずあくまで個人的意見なのだが初代ぷよぷよの相殺なしルールは ぷよぷよと認めていないので相殺は必須だろう. また,フィーバーのように相殺している限りジャマーは降らないというルールは フィーバーモードがあるから活きるわけである. よって今回は素直にぷよぷよ通のルールを採用しよう.~ すなわち - ブロックを消すとライバルにジャマーが発生する - 連鎖中に蓄積されているジャマーはその連鎖が終了するまで降らない -- この状態のジャマーはジャマーバッファにたまる -- 連鎖が終わるとバッファのジャマーは以前つくった確定ジャマー((@jammers))に移動する - ジャマーが蓄積している場合ブロックを消すことで相殺することができる - ジャマー落下フェーズに移行した時,確定ジャマーが存在する場合ジャマーが落下する #sh(ruby){{ class JammerManager attr_accessor :rivals_buf def initialize(row_s, line_s) (略) @rivals_buf = {} @rivals = [] init_order end def rivals=(rivals) @rivals = rivals.map{|rival| rival.field.jm } @rivals_buf = {} @rivals.each do |rival| @rivals_buf[rival] = 0 end end def total_jammers self.jammers + self.total_rivals_buf end def total_rivals_buf @rivals_buf.inject(0){|sum,(rival,num)| sum + num} end def offset_rivals_buf(num) return num if @rivals.empty? offset_per = [num / @rivals.size, 1].max rest = num @rivals.shuffle.each do |rival| return 0 if rest == 0 if rest >= offset_per && @rivals_buf[rival] >= offset_per @rivals_buf[rival] -= offset_per rest -= offset_per elsif @rivals_buf[rival] >= rest @rivals_buf[rival] -= rest rest = 0 else rest -= @rivals_buf[rival] @rivals_buf[rival] = 0 end end rest = offset_rivals_buf(num) if rest < num rest end def store_buf(num) # offset certain jammers rest = num - self.jammers self.jammers -= num return if rest <= 0 # offset buffered jammers rest = offset_rivals_buf(rest) # attack @rivals.each do |rival| next if rival.rivals_buf[self].nil? rival.rivals_buf[self] += rest end end def establish_rival_buf(rival) return if @rivals_buf[rival].nil? @jammers += @rivals_buf[rival] @rivals_buf[rival] = 0 end def establish_jammers @rivals.each do |rival| rival.establish_rival_buf(self) end end end }} ジャマーマネージャーが新しく持つ要素として@rivalsと@rivals_bufがある. @rivalsは先程説明した通り敵となるフィールドコントローラーのジャマーマネージャーである.~ @rivals_bufはライバルの連鎖によって発生したジャマーをためるハッシュで, ライバルごとにジャマー数が管理される. ライバルが連鎖をするとこのバッファにジャマーが蓄積され,連鎖が終わるとバッファから確定ジャマーへ移動する.~ これらの処理はライバル側のジャマーマネージャーからsore_buf,establish_rivals_bufメソッドを呼ばれることで行われる. この処理により,あとは今までの設計のままでうまくジャマーが落下するようになっている. 相殺については,まずは確定ジャマーが存在すれば優先的に数を減らし, ライバルのバッファにジャマーが存在すればバッファから数を減らす.~ このときどのライバルのバッファからどのくらい減らすかがよくわからないが, ひとまず以下の再帰的な戦略をとることにした. - 減らすことのできるジャマー数の合計をrestとする - ライバル一人あたり減少させるジャマー数を求める(単純に等分) - ライバルごとにバッファの数を減らす -- このとき減少した分をrestから減らしておく - restが0ならもう減らせないので終了,0を返す - restが減少していなければそもそもバッファにジャマーがないので終了,restを返す - それ以外ならまだ減らせるバッファがあるかもしれないので余ったrestでもう一度一連の処理を行い,その戻り値を返す これを実装したのがoffset_rivals_bufメソッドである. そしてこれら相殺を行った後,それでも余ったジャマーがライバルのジャマーバッファ に加算される.~ ライバルが複数いたときジャマーをどう分散するかといった問題があるにはあるが, とりあえず等分などは行わずにライバル全員に発生したジャマーの数だけジャマーバッファに加算することにした. #sh(ruby){{ class Field attr_reader :jm def rivals=(rivals) @jm.rivals = rivals end def eliminate (略) unless @connect_table.empty? jammers = @jm.calc_jammer(@sm.chain_score, @jammer_rate, scene.margin_time, scene.playtime) @jm.store_buf(jammers) end (略) end end }} #sh(ruby){{ class FieldController attr_reader :field def rivals=(rivals) @field.rivals = rivals end def init_phase (略) @phase.add_end_handler(:eliminate, :fall_jammer, method(:end_eliminate_to_fall_jammer)) (略) end def end_eliminate_to_fall_jammer # establish jammers @field.jm.establish_jammers end end }} *** 実行 [#y0bc4101] デバッグ表示の右側がジャマーに関する情報である. 合計ジャマー = 確定ジャマー + ジャマーバッファ という形式になっている. プレイヤー2が連鎖中はジャマーバッファに蓄積されてジャマーは降らない. その後プレイヤー1が相殺し連鎖が終わるとプレイヤー2の確定ジャマーに移動し, ジャマーが降っている様子がわかる. #media(PuyoPuyoChap31/PuyoPuyoChap31_1.flv) 一人二役でいい相殺の例はとても無理なので,久しぶりにset_tableを使った. ** ゲームオーバーの処理 [#jca7e88f] これでほぼ対戦のゲームモードは完成したわけだが, 今のままではどちらかがゲームオーバーになってももう一方がゲームオーバーにならない 限りゲームが終了しない.負けたら暇である.~ そこでゲームオーバーに関してもうまくフィールドコントローラー同士でやり取りする機能を追加しよう. この機能のためには何が必要かといえばフェーズが終了しているかどうかだろう. この場フィールドコントローラーに関して - 死んでいるか((dead?がtrue)) - フェーズがgameoverか~ この二点を調べるメソッドを作ればなんとかなりそうだ. #sh(ruby){{ class FieldController def initialize(x,y,row_s, line_s, block_s) (略) @rivals = [] (略) end def rivals=(rivals) @rivals = rivals @field.rivals = rivals end def init_phase (略) # added :win handler @phase.add_condition_handler(:win, method(:win_cond)) @phase.change :control_block end def gameover? @phase.phase == :gameover end def rivals_gameover? return false if @rivals.empty? @rivals.all? {|rival| rival.dead? || rival.gameover? } end def update_control_block inputs = [input_move_row?,input_rotate?, input_fastfall?,input_momentfall?] active = @field.update_control_block(*inputs) if active @phase.change :win if rivals_gameover? else @phase.change :falldown end end def update_eliminate eliminated = @field.eliminate if eliminated @phase.change :falldown else @phase.change rivals_gameover? ? :win : :fall_jammer end end def update_win @phase.wait(60) @phase.change :term end def win_cond @phase.kill end end }} rivalsのセットメソッドにフィールドへライバルを伝達する前に自分自身もライバルのフィールドコントローラーを保持するようにした.これでライバルの状態がわかる. あとは次の状態のときライバル全員がゲームオーバーであれば winフェーズへと移行すれば完成である. - 連鎖が終了した時((eliminateフェーズの終了時)) - control_blockフェーズの時 連鎖中はwinフェーズへ移行しない仕様になっている.これは一度発火した連鎖は最後まで見たいという要望から生まれた仕様だろう((全くの推測だが)). winフェーズはgameoverフェーズと全く同じなので説明は不要だろう. *** 実行 [#k14971a3] これでどちらかがゲームオーバーになればゲームが終了するようになった. #media(PuyoPuyoChap31/PuyoPuyoChap31_2.flv)