2009年11月21日土曜日

Drools で三目並べ(コード)

前ポストの Drools 三目並べのコード。

■ ルール定義

ブログにしては、ちょっと多めの行数だけど一行当たりの文字数が少ないので、記述量は全然大した事無い(以下、prettyprint が固まり気味なので小分けする)。

package tictactoe
 
import tictactoe.Cell;
import tictactoe.Row;
import tictactoe.Game;
import tictactoe.Game.State;

rule "初期化"
   when
      game: Game(state == State.INITIALIZED)
   then
      for (Row row: game.getRows()) insert(row);
      for (Cell[] row: game.getBoard()) { 
         for (Cell cell: row) insert(cell);
      }
      modify(game) { setState(State.START); }
end

rule "開始"
   when
      game: Game(state == State.START)
   then
      modify(game) { start(); }
end
rule "マシン側戦略1" @Strategy(Win)
salience 70
   when
      game: Game(state == State.MACHINE_TURN)
      row:  Row($cells: cells)
      c1:   Cell(state == Cell.MACHINE) from $cells 
      c2:   Cell(state == Cell.MACHINE) from $cells
      c3:   Cell(state == Cell.FREE) from $cells
      eval (c1 != c2)
   then
      modify(c3) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end

rule "マシン側戦略2" @Strategy(Block)
salience 60
   when
      game: Game(state == State.MACHINE_TURN)
      row:  Row($cells: cells)
      c1:   Cell(state == Cell.USER) from $cells 
      c2:   Cell(state == Cell.USER) from $cells 
      c3:   Cell(state == Cell.FREE) from $cells 
      eval (c1 != c2)
   then
      modify(c3) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end
rule "マシン側戦略3" @Strategy(Fork)
salience 50
   when
      game: Game(state == State.MACHINE_TURN)
      c:    Cell(state == Cell.FREE)
      mc1:  Cell(state == Cell.MACHINE)
      mc2:  Cell(state == Cell.MACHINE)
      fc1:  Cell(state == Cell.FREE)
      fc2:  Cell(state == Cell.FREE)
      r1:   Row(cells contains c, cells contains mc1, cells contains fc1)
      r2:   Row(cells contains c, cells contains mc2, cells contains fc2)
      eval (r1 != r2 && c != fc1 && c != fc2)
   then
      modify(c) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end

rule "マシン側戦略4" @Strategy(Block Opponent^s Fork)
salience 40
   when
      game: Game(state == State.MACHINE_TURN)
      c:    Cell(state == Cell.FREE)
      mc1:  Cell(state == Cell.USER)
      mc2:  Cell(state == Cell.USER)
      fc1:  Cell(state == Cell.FREE)
      fc2:  Cell(state == Cell.FREE)
      r1:   Row(cells contains c, cells contains mc1, cells contains fc1)
      r2:   Row(cells contains c, cells contains mc2, cells contains fc2)
       eval (r1 != r2 && c != fc1 && c != fc2)
   then
      modify(c) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end
rule "マシン側戦略5" @Strategy(Center)
salience 30
   when
      game: Game(state == State.MACHINE_TURN)
       c: Cell(state == Cell.FREE, x == 1, y == 1)
   then
      modify(c) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end

rule "マシン側戦略6" @Strategy(Opposite Corner)
salience 20
   when
      game: Game(state == State.MACHINE_TURN)
      uc:   Cell(state == Cell.USER, $ux: x, $uy: y)
      mc:   Cell(state == Cell.FREE, $mx: x, $my: y)
      eval (($ux==0 && $mx==2 || $ux==2 && $mx==0) 
          && ($uy==0 && $my==2 || $uy==2 && $my==0))
   then
      modify(mc) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end
rule "マシン側戦略7" @Strategy(Empty Corner)
salience 10
   when
      game: Game(state == State.MACHINE_TURN)
      c: Cell(state == Cell.FREE, x != 1, y != 1)
   then
      modify(c) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end

rule "マシン側戦略8" @Strategy(Empty Side)
   when
      game: Game(state == State.MACHINE_TURN)
      c: Cell(state == Cell.FREE)
   then
      modify(c) { setState(Cell.MACHINE); }
      modify(game) { setState(State.JUDGING); }
end
rule "ユーザの番"
   when
      game: Game(state == State.USER_TURN)
   then
      update(game.play());
      modify(game) { setState(State.JUDGING); }
end

rule "決着"
salience 20
   when
      game: Game(state == State.JUDGING)
      row:  Row($cells: cells)
      c1:   Cell($s: state, state != Cell.FREE) from $cells
      c2:   Cell(state == $s) from $cells
      c3:   Cell(state == $s) from $cells
      eval (c1 != c2 && c2 != c3 && c3 != c1)
   then
      System.out.printf("== %s won!%n",
            (Cell.MACHINE == $s) ? "machine": "user");
      modify(game) { setState(State.FINISHED); }
end

rule "引き分け"
salience 10
   when
      game: Game(state == State.JUDGING)
      not Cell(state == Cell.FREE)
   then
      System.out.println("== draw!");
      modify(game) { setState(State.FINISHED); }
end

rule "続行"
   when
      game: Game(state == State.JUDGING)
   then
      modify(game) { change(); }
end
(割とすっきり書けたと思うが、いかにも Java なコードがまだまだ少なくないので、その辺りもう少し工夫したい。)

■ Java コード

ルール定義を見れば察しが付くようなコードばかりなので面白く無いので、以下のエベントリスナの定義を含む main() だけ抜粋。

 public static final void main(String[] args) throws Exception {
    KnowledgeBase kbase = readKnowledgeBase();
    StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
    final Game game = new Game();
    ksession.insert(game);
    ksession.addEventListener(new DefaultAgendaEventListener() {
       public void afterActivationFired(AfterActivationFiredEvent event) {
           Rule rule = event.getActivation().getRule();
           String strategy = rule.getMetaAttribute("Strategy");
           if (null != strategy) {
              System.out.printf(" strategy = %s%n", strategy);
           } else if (Arrays.asList(new String[]{
                   "続行", "引き分け", "決着"}).contains(rule.getName())) {
               game.printBoard();
           }
       }
    });
    ksession.fireAllRules();
 }
この辺りをもう一工夫すれば、ルール定義 と Java コードをもっときれいに分離できそうな気がする。

0 件のコメント:

コメントを投稿