MOE3: 単純移動命令の解決

MainPhase#resolve_move メソッド

先にテストを書く。

resolve_move 自体は private メソッドの位置付けで*1、実装が進むと名前や扱いが変わる可能性がある、というかその余地を縛りたくないので、テストはあくまで MainPhase#resolve_orders を対象としたものになる。

spec/main_phase_spec.rb

マニュアルの Diagram1 から French A Par-Bur を実装する。

f:id:asagix:20130910224004p:plain:w200

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

    shared_context "単純移動 Diagram1", diagram: 1 do
      let(:army_par) { FactoryGirl.create(:army, :f, :par, phase: phase) }
      let(:prov_bur) { Province.where(code: "bur").first }

      let!(:move_par_bur) { FactoryGirl.create(:move_order, unit: army_par, destination: prov_bur) }
    end

    context "Diagram1", diagram: 1 do
      subject { resolved_orders }
      example { expect(subject.find(move_par_bur).status).to eq OrderStatus::SUCCESS }
    end
  end
end

MainPhase#resolve_orders 実行後、命令ステータスが OrderStatus::SUCCESS になれば良い。

MainPhase#resolve_orders

では実装しよう。

事前に実験したところ ActiveRecord の遅延評価とキャッシュが邪魔になりそうだったので*2、予めクエリ結果を配列として取得しておき、全てをオンメモリで処理した後に一括で save! することにした。

class MainPhase < Phase
  def resolve_orders
    setup
    resolve_move
    cleanup

    orders(true)
  end

  def setup
    @orders = orders.where(sample: false).to_a
  end

  def resolve_move
    moves = @orders.select{|o| o.move? && o.unexecuted?}
    moves.collect{|m| m.dst}.uniq.each do |dst|
      holds = @orders.select{|o| o.hold? && o.province == dst}
      return if holds.size != 0

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

      move.success!
    end
  end

  def cleanup
    @orders.each do |order|
      order.save!
    end
  end
end

setup

事前準備として、orders のクエリに対して「標本命令以外」を指定して取得した結果を配列化している。

いずれ「仮想命令以外」も条件として追加するが、こちらはクエリ条件ではなく、配列化後に Array#select で絞り込むのが楽なはずだ。

そのためには、命令者の担当国とユニットの所属国が異なる場合に true を返してくれる Order#assumed? メソッドが欲しいので Player モデルの実装までお預け。

resolve_move

戦力の実装はもう少し先なので、ここでは戦力評価抜きで確定する移動命令を確定させてしまう。

MainPhase#resolve_move の仕様

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

Order#hold?Order#move?Order#unexecuted?Order#success! は楽をするために導入。hold?move? は派生クラスで適宜オーバーライドする。

MoveOrder#hold? は基本的に false を返すが、移動失敗が確定していたら true を返すと便利になるような気がしないでもない。

cleanup

この時点ですべての命令に対する処理が完了しているので一つずつ DB に保存する。

*1:明示的に宣言するのは必要に迫られてからで良いのだ。

*2:頻繁なステータスの書き換えが発生する行軍解決処理とはすこぶる相性が悪かった。