MOE3: 支援付きスタンドオフの解決

spec/models/main_phase_spec.rb

さくさく行こう。

マニュアルの Diagram10、French F Gol-TynFrench F Wes S F Gol-TynItalian F Nap-TynItalian F Rom S F Nap-Tyn の支援付きスタンドオフを実装する。

f:id:asagix:20130919100324p:plain:w200

ちなみに MOE3 の地名は jDip 準拠なので、"Gulf of Lyon" の略称は "Gol" ではなく "Lyo" とする。

describe MainPhase do
  describe "#resolve_orders" do
    let(:phase) { MainPhase.create }
    let(:resolved_orders) { phase.resolve_orders }

    #(略)

    shared_context "Diagram10 支援付きスタンドオフ", diagram: 10 do
      let(:fleet_lyo) { FactoryGirl.create(:fleet, :f, :lyo, phase: phase) }
      let(:fleet_wes) { FactoryGirl.create(:fleet, :f, :wes, phase: phase) }
      let(:fleet_nap) { FactoryGirl.create(:fleet, :i, :nap, phase: phase) }
      let(:fleet_rom) { FactoryGirl.create(:fleet, :i, :rom, phase: phase) }

      let(:prov_tyn) { Province.find_by(code: "tyn") }

      let!(:move_lyo_tyn) { FactoryGirl.create(:move_order, unit: fleet_lyo, destination: prov_tyn) }
      let!(:supp_wes_lyo) { FactoryGirl.create(:support_order, unit: fleet_wes, target: move_lyo_tyn) }
      let!(:move_nap_tyn) { FactoryGirl.create(:move_order, unit: fleet_nap, destination: prov_tyn) }
      let!(:supp_rom_nap) { FactoryGirl.create(:support_order, unit: fleet_rom, target: move_nap_tyn) }
    end

    context "Diagram10", diagram: 10 do
      subject { resolved_orders }
      example { expect(subject.find(move_lyo_tyn).status).to eq OrderStatus::STANDOFF }
      example { expect(subject.find(supp_wes_lyo).status).to eq OrderStatus::SATISFIED }
      example { expect(subject.find(move_nap_tyn).status).to eq OrderStatus::STANDOFF }
      example { expect(subject.find(supp_rom_nap).status).to eq OrderStatus::SATISFIED }
    end
  end
end

MainPhase#resolve_standoff メソッド

既存のコードに手を入れることなくテストが通ってしまった。

戦力が同等なので当たり前。

  # スタンドオフ
  def resolve_standoff
    moves = @orders.select{|o| o.move?}
    moves.collect{|m| m.dst}.uniq.each do |dst|
      conflicts = moves.select{|m| m.dst == dst}
      next if conflicts.size == 1

      conflicts.each do |move|
        move.standoff!
      end
    end
  end

spec/models/main_phase_spec.rb

テストを追加する。

Diagram10 を改変し、Italian F Rom S F Nap-Tyn を外す。

これで French F Gol-TynSUCCESSItalian F Nap-TynFAILURE になれば成功。

describe MainPhase do
  describe "#resolve_orders" do
    let(:phase) { MainPhase.create }
    let(:resolved_orders) { phase.resolve_orders }

    #(略)

    shared_context "Diagram10.1 戦力差のある同一地域への移動", diagram: 10.1 do
      let(:fleet_lyo) { FactoryGirl.create(:fleet, :f, :lyo, phase: phase) }
      let(:fleet_wes) { FactoryGirl.create(:fleet, :f, :wes, phase: phase) }
      let(:fleet_nap) { FactoryGirl.create(:fleet, :i, :nap, phase: phase) }

      let(:prov_tyn) { Province.find_by(code: "tyn") }

      let!(:move_lyo_tyn) { FactoryGirl.create(:move_order, unit: fleet_lyo, destination: prov_tyn) }
      let!(:supp_wes_lyo) { FactoryGirl.create(:support_order, unit: fleet_wes, target: move_lyo_tyn) }
      let!(:move_nap_tyn) { FactoryGirl.create(:move_order, unit: fleet_nap, destination: prov_tyn) }
    end

    context "Diagram10.1", diagram: 10.1 do
      subject { resolved_orders }
      example { expect(subject.find(move_lyo_tyn).status).to eq OrderStatus::SUCCESS }
      example { expect(subject.find(move_nap_tyn).status).to eq OrderStatus::FAILURE }
    end
  end
end

そのままテストを実行すると、もちろん NG。

さあ、MainPhase#resolve_standoff を修正しよう。

メソッド名も変更した方が良さそうだな。resolve_conflicts でいいか。

resolve_standoff 改め reslove_conflicts メソッド

  # 同一地域への移動
  def resolve_conflicts
    moves = @orders.select{|o| o.move?}
    moves.collect{|m| m.dst}.uniq.each do |dst|
      conflicts = moves.select{|m| m.dst == dst}
      next if conflicts.size == 1

      # 戦力抽出
      supported_moves = {}
      conflicts.each do |move|
        supported_moves[move] = @orders.select{|o| o.support? && o.target?(move)}.size
      end

      # 最大戦力取得
      max_supports = supported_moves.values.sort.last

      if supported_moves.select{|k,v| v == max_supports}.size != 1
        # 最大戦力が同等の移動が複数ある場合は全てスタンドオフ
        conflicts.each do |move|
          move.standoff!
        end
        # スタンドオフ地域に維持ユニットがいたら維持成功とする
        hold = @orders.select{|o| o.hold? && o.province == dst}[0]
        if hold && hold.unexecuted?
          hold.success!
        end
      else
        # 最大戦力の移動以外は失敗
        conflicts.each do |move|
          next if move == supported_moves.key(max_supports)
          move.failure!
        end
      end
    end
  end

移動先に維持ユニットがいたら別途攻撃成否判定が必要になるので、このタイミングでは最大戦力の移動命令を SUCCESS にできない。

「スタンドオフ地域に維持ユニットがいたら~」は少々気の回し過ぎかもしれないが、どうせいずれは必要になるので入れておく。

MainPhase#cleanup メソッド

このままだと French F Gol-TynUNEXECUTED のまま処理が終了してしまうので、MainPhase#cleanup で未処理命令を一律 SUCCESS にする処理を入れてお茶を濁す。どこからも干渉されない単品の維持命令などもこれで成功確定できる。

  def cleanup
    @orders.each do |order|
      order.success! if order.unexecuted?
      order.save!
    end
  end

だが、美しくない。そのうちなんとかする。