MOE3: 玉突き衝突の解決

spec/models/main_phase_spec.rb

テストを書く。

マニュアルの Diagram5、German A Ber–PruGerman A Kie–Ber の移動失敗の連鎖処理を実装する。

f:id:asagix:20130912114125p:plain:w200

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

    #(略)

    shared_context "Diagram5 玉突き衝突", diagram: 5 do
      let(:army_pru) { FactoryGirl.create(:army, :r, :pru, phase: phase) }
      let(:army_ber) { FactoryGirl.create(:army, :g, :ber, phase: phase) }
      let(:army_kie) { FactoryGirl.create(:army, :g, :kie, phase: phase) }
      let(:prov_pru) { army_pru.province }
      let(:prov_ber) { army_ber.province }

      let!(:hold_pru) { FactoryGirl.create(:hold_order, unit: army_pru) }
      let!(:move_ber_pru) { FactoryGirl.create(:move_order, unit: army_ber, destination: prov_pru) }
      let!(:move_kie_ber) { FactoryGirl.create(:move_order, unit: army_kie, destination: prov_ber) }
    end

    context "Diagram5", diagram: 5 do
      subject { resolved_orders }
      example { expect(subject.find(move_ber_pru).status).to eq OrderStatus::FAILURE }
      example { expect(subject.find(move_kie_ber).status).to eq OrderStatus::FAILURE }
    end
  end
end

MainPhase#resolve_pileup メソッド

メソッド名が適当になってきた。

MainPhase#resolve_pileup の仕様

  • 移動先が塞がってたら移動失敗。
  • 移動先に未解決の移動命令があったら処理保留。
  • 玉突き衝突解決のためにループ処理。

まず MoveOrder#hold? に手を入れる。

class MoveOrder < Order
  #(略)

  def hold?
    # 失敗した移動命令は維持と同様に扱う
    return true if failure?
    return true if standoff?
    false
  end

  #(略)
end

MainPhase#resolve_move もちょっと修正。

MainPhase#resolve_move の仕様

  • 移動先に維持命令を受けた軍があったら処理保留。
  • 移動先が競合する移動命令があったら処理保留。
  • 移動先に未解決の移動命令があったら処理保留。 ←New!
  • 残りは移動成功。

現時点では未処理命令に交換移動セット*1や循環移動セット*2があると、resolve_pileup で無限ループに突入してしまうので注意。

交換移動は Diagram6、循環移動は Diagram7 で実装する。

class MainPhase < Phase
  def resolve_orders
    setup
    resolve_move
    resolve_standoff
    resolve_pileup 
    cleanup
    orders(true)
  end

  #(略)

  # 障害のない移動
  def resolve_move
    moves = @orders.select{|o| o.move? && o.unexecuted?}
    moves.collect{|m| m.dst}.uniq.each do |dst|
      hold = @orders.select{|o| o.hold? && o.province == dst}[0]
      next if hold

      move = @orders.select{|o| o.move? && o.src == dst}[0]
      next if move

      move, *conflicts = moves.select{|m| m.dst == dst}
      next if conflicts.size != 0

      move.success!
    end
  end

  #(略)

  # 玉突き衝突
  def resolve_pileup 
    loop do
      moves = @orders.select{|o| o.move?}
      break if moves.size == 0

      moves.collect{|m| m.dst}.uniq.each do |dst|
        hold = @orders.select{|o| o.hold? && o.province == dst}[0]
        next unless hold

        moves.select{|m| m.dst == dst}.each do |m1|
          m1.failure!
        end
      end
    end
  end

  #(略)
end

ついでに Order#failure! も実装。

class Order <  ActiveRecord::Base
  #(略)

  def failure!
    self.status = OrderStatus::FAILURE
  end

  #(略)
end

メソッド名は Order#fail! の方が良さげかもしれない。後で考える。

*1:A→B、B→A みたいなやつ。

*2:A→B、B→C、C→A みたいなやつ。