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


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