MOE3: 玉突き衝突の解決
spec/models/main_phase_spec.rb
テストを書く。
マニュアルの Diagram5、German A Ber–Pru
と German A Kie–Ber
の移動失敗の連鎖処理を実装する。
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!
の方が良さげかもしれない。後で考える。