対戦しよう †ゲームモードが作れるようになったので早速新しいゲームモードを作ろう. ここはやはりプレイヤー同士の対戦が作りたいところだ. 幸い2プレイヤーのためのフィールドコントローラーはすでに作っておいたので*1, 今回作るべきはフィールドコントローラー間のやり取りである. 新しいゲームモード †まずは新しいゲームモードのためのシーンを作ってしまおう. SceneTwoPlayer?*2は二人のプレイヤーが 対戦するゲームモードである. とはいっても多人数プレイを意識した設計になっているので特に気を張る必要はない. ひとつシングルモードと異なるのはrivalsだろう. ここに敵となるフィールドコントローラーの配列をセットすることで, 自動的にジャマーが相手に発生するようになる予定だ. 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 ジャマーの発生と相殺 †フィールドコントローラーでセットするライバルの配列は フィールドクラスを経てジャマーマネージャーの@rivalsにセットされる. このとき登録される配列の中身はライバルのジャマーマネージャーに変換される. ここで再びジャマーに関する考察を行おう.
まずあくまで個人的意見なのだが初代ぷよぷよの相殺なしルールは
ぷよぷよと認めていないので相殺は必須だろう.
また,フィーバーのように相殺している限りジャマーは降らないというルールは
フィーバーモードがあるから活きるわけである.
よって今回は素直にぷよぷよ通のルールを採用しよう.
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は先程説明した通り敵となるフィールドコントローラーのジャマーマネージャーである. この処理により,あとは今までの設計のままでうまくジャマーが落下するようになっている. 相殺については,まずは確定ジャマーが存在すれば優先的に数を減らし,
ライバルのバッファにジャマーが存在すればバッファから数を減らす.
これを実装したのがoffset_rivals_bufメソッドである. そしてこれら相殺を行った後,それでも余ったジャマーがライバルのジャマーバッファ
に加算される. 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 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 実行 †デバッグ表示の右側がジャマーに関する情報である. 合計ジャマー = 確定ジャマー + ジャマーバッファ という形式になっている. プレイヤー2が連鎖中はジャマーバッファに蓄積されてジャマーは降らない. その後プレイヤー1が相殺し連鎖が終わるとプレイヤー2の確定ジャマーに移動し, ジャマーが降っている様子がわかる. Loading the player ...
一人二役でいい相殺の例はとても無理なので,久しぶりにset_tableを使った. ゲームオーバーの処理 †これでほぼ対戦のゲームモードは完成したわけだが,
今のままではどちらかがゲームオーバーになってももう一方がゲームオーバーにならない
限りゲームが終了しない.負けたら暇である. この機能のためには何が必要かといえばフェーズが終了しているかどうかだろう. この場フィールドコントローラーに関して
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フェーズへと移行すれば完成である.
連鎖中はwinフェーズへ移行しない仕様になっている.これは一度発火した連鎖は最後まで見たいという要望から生まれた仕様だろう*6. winフェーズはgameoverフェーズと全く同じなので説明は不要だろう. 実行 †これでどちらかがゲームオーバーになればゲームが終了するようになった. Loading the player ...
|