7.8. Conway の Game of Life 例のデシジョン (ルールフローグループおよび GUI 統合)
John Conway による有名なセルオートマトン (CA: Cellular automation) をベースにした Conway の Game of Life 例のデシジョンセットは、ルールでルールフローグループを使用してルール実行を制御する方法を例示します。またこの例は、Red Hat Decision Manager ルールをグラフィカルユーザーインターフェイス (GUI) と統合する方法も例示しています。今回は、Conway の Game of Life を Swing ベースで実装しています。
以下は、Conway の Game of Life の例の概要です。
-
名前:
conway
-
Main クラス: (
src/main/java
内の)org.drools.examples.conway.ConwayRuleFlowGroupRun
、org.drools.examples.conway.ConwayAgendaGroupRun
-
モジュール:
droolsjbpm-integration-examples
- タイプ: Java アプリケーション
-
ルールファイル: (
src/main/resources
内の)org.drools.examples.conway.*.drl
- 目的: ルールフローグループと GUI 統合を例示します。
Conway の Game of Life の例は、Red Hat Decision Manager に含まれる他のデシジョンセットの例の多くとは異なり、Red Hat カスタマーポータル
から取得する Red Hat Decision Manager 7.2.0 Source Distribution の ~/rhdm-7.2.0-sources/src/droolsjbpm-integration-$VERSION/droolsjbpm-integration-examples に保存されています。
Conway の Game of Life では、初期設定または定義済みのプロパティーで高度なパターンを作成して、初期状態からどのように進化していくかを観察することで、ユーザーはゲームと対話します。ゲームの目的は、世代ごとに人口の成長を表示します。各世代は、すべてのセル (細胞) が同時に進化していき、前の世代をもとにして生み出されます。
以下の基本的なルールで、次の世代がどのようになるかを制御していきます。
- 生きているセルの近傍に、生きているセルが 2 個未満の場合は、孤独で死んでしまう。
- 生きているセルの近傍に、生きているセルが 4 個以上ある場合は、過密で死んでしまう。
- 死亡したセルの近傍に、生きているセルがちょうど 3 つある場合には、このセルは生き返る。
この基準のいずれも満たさないセルは、そのまま次の世代に残ります。
Conway の Game of Life の例は、ruleflow-group
属性が含まれる Red Hat Decision Manager ルールで、ゲームに実装されているパターンを定義します。この例には、アジェンダグループを使用して同じ動作を行うデシジョンセットのバージョンも含まれています。アジェンダグループを使用すると、エンジンアジェンダを分割し、ルールのグループの実行を制御できるようになります。デフォルトでは、ルールはすべてアジェンダグループ MAIN
に含まれています。agenda-group
属性を使用して、ルールに異なるアジェンダグループを指定できます。
この概要では、Conway の例でアジェンダグループを使用したバージョンは触れません。アジェンダグループの詳細情報は、特にアジェンダグループについて対応している Red Hat Decision Manager 例のデシジョンセットを参照してください。
Conway 例の実行および対話
他の Red Hat Decision Manager のデシジョン例と同じように、お使いの IDE で org.drools.examples.conway.ConwayRuleFlowGroupRun
クラスを Java アプリケーションとして実行し、Conway の例を実行します。
Conway の例を実行すると、Conway's Game of Life
GUI ウィンドウが表示されます。このウィンドウには、空のグリッドまたはアリーナが含まれており、ここで生命のシミュレーションが行われます。システムにまだ生きているセルが含まれていないため、グリッドは最初は空白です。
図7.21 起動後の Conway 例の GUI
パターン のドロップダウンメニューから事前定義済みのパターンを選択して、次の世代 をクリックし、各人口の世代をクリックしていきます。セルは生きているか、死んでいるかのどちらかで、生きているセルには緑のボールが含まれます。最初のパターンから人口が進化するにつれ、ゲームのルールをもとに、セルが近傍のセルに合わせて、生存するか、死亡していきます。
図7.22 Conway の例の世代進化
近傍には、上下左右のセルだけでなく対角線上につながっているセルも含まれるため、各セルには合計 8 つの近傍があります。例外は、角のセルと 4 辺上にあるセルで、それぞれ順に近傍が 3 つだけと、5 つだけになります。
セルをクリックすることで手動で介入して、セルを作成することも、死亡させることもできます。
最初のパターンから自動的に進化を実行するには、スタート をクリックします。
ルールグループを使用する Conway 例のルール
ConwayRuleFlowGroupRun
の例のルールは、ルールフローグループを使用して、ルール実行を制御します。ルールフローグループは、ruleflow-group
ルール属性に関連付けられたルールのグループです。これらのルールは、このグループがアクティベートされたときにしか実行されません。グループ自体は、ルールフローの図の詳細がグループを表すノードに到達してからでないと、アクティブになりません。
Conway の例では、ルールに以下のルールフローグループを使用します。
-
"register neighbor"
-
"evaluate"
-
"calculate"
-
"reset calculate"
-
"birth"
-
"kill"
-
"kill all"
Cell
オブジェクトはすべて KIE セッションに挿入され、"register neighbor"
ルールフローグループの "register …"
ルールがルールフロー処理により実行できるようになります。4 つのルールが含まれるこのグループは、セル同士の Neighbor の関係と、北東、北、北西、西の近傍との Neighbor
の関係を作り出します。
この関係は双方向で、他の 4 方向を処理します。各辺上のセルは、特別な対応は必要ありません。これらのセルは、近傍のセルがなければペアは作成されません。
これらのルールに対して、すべてのアクティベーションが実行されるまで、全セルは、近傍の全セルと関係があります。
ルール "register …"
rule "register north east" ruleflow-group "register neighbor" when $cell: Cell( $row : row, $col : col ) $northEast : Cell( row == ($row - 1), col == ( $col + 1 ) ) then insert( new Neighbor( $cell, $northEast ) ); insert( new Neighbor( $northEast, $cell ) ); end rule "register north" ruleflow-group "register neighbor" when $cell: Cell( $row : row, $col : col ) $north : Cell( row == ($row - 1), col == $col ) then insert( new Neighbor( $cell, $north ) ); insert( new Neighbor( $north, $cell ) ); end rule "register north west" ruleflow-group "register neighbor" when $cell: Cell( $row : row, $col : col ) $northWest : Cell( row == ($row - 1), col == ( $col - 1 ) ) then insert( new Neighbor( $cell, $northWest ) ); insert( new Neighbor( $northWest, $cell ) ); end rule "register west" ruleflow-group "register neighbor" when $cell: Cell( $row : row, $col : col ) $west : Cell( row == $row, col == ( $col - 1 ) ) then insert( new Neighbor( $cell, $west ) ); insert( new Neighbor( $west, $cell ) ); end
全セルが挿入されたら、Java コードはグリッドにパターンを適用し、特定のセルを Live
に設定します。次に、ユーザーが スタート または 次の世代 をクリックすると、Generation
のルールフローが実行されます。このルールフローは、世代のサイクルごとにセルの変更をすべて管理します。
図7.23 世代のルールフロー
ルールフロープロセスは、実行可能なグループに "evaluate"
ルールフローグループおよびアクティブなルールを追加します。このグループの "Kill the …"
と "Give Birth"
ルールを使用して、細胞の誕生または死亡セルにゲームのルールを適用します。この例では、phase
属性を使用して、特定のルールグループで Cell
オブジェクトの推論をトリガーします。通常は、フェーズはルールフロープロセス定義に含まれるルールフローグループに紐づけされています。
この例では、変更の適用前に評価を完全に完了しておく必要があるため、この時点では Cell
オブジェクトの状態は変更されません。細胞の phase
を Phase.KILL
または Phase.BIRTH
に適用し、後ほど Cell
オブジェクトに適用されたアクションを制御するのに使用します。
ルール "Kill the …" および "Give Birth"
rule "Kill The Lonely" ruleflow-group "evaluate" no-loop when // A live cell has fewer than 2 live neighbors. theCell: Cell( liveNeighbors < 2, cellState == CellState.LIVE, phase == Phase.EVALUATE ) then modify( theCell ){ setPhase( Phase.KILL ); } end rule "Kill The Overcrowded" ruleflow-group "evaluate" no-loop when // A live cell has more than 3 live neighbors. theCell: Cell( liveNeighbors > 3, cellState == CellState.LIVE, phase == Phase.EVALUATE ) then modify( theCell ){ setPhase( Phase.KILL ); } end rule "Give Birth" ruleflow-group "evaluate" no-loop when // A dead cell has 3 live neighbors. theCell: Cell( liveNeighbors == 3, cellState == CellState.DEAD, phase == Phase.EVALUATE ) then modify( theCell ){ theCell.setPhase( Phase.BIRTH ); } end
グリッド内の全 Cell
オブジェクトが評価されると、この例では "reset calculate"
ルールを使用して "calculate"
ルールフローグループのアクティベーションを消去します。次に、ルールフローグループがアクティベートされると、"kill"
と "birth"
のルールを有効にするルールフローに分岐を挿入します。これらのルールにより状態の変更が適用されます。
ルール "reset calculate"、"kill"、および "birth"
rule "reset calculate" ruleflow-group "reset calculate" when then WorkingMemory wm = drools.getWorkingMemory(); wm.clearRuleFlowGroup( "calculate" ); end rule "kill" ruleflow-group "kill" no-loop when theCell: Cell( phase == Phase.KILL ) then modify( theCell ){ setCellState( CellState.DEAD ), setPhase( Phase.DONE ); } end rule "birth" ruleflow-group "birth" no-loop when theCell: Cell( phase == Phase.BIRTH ) then modify( theCell ){ setCellState( CellState.LIVE ), setPhase( Phase.DONE ); } end
この段階では、複数の Cell
オブジェクトの状態が LIVE
または DEAD
のいずれかに変更されています。この例では、細胞が生存または死亡すると、"Calculate …"
ルールの Neighbor
関係を使用して、周辺のすべての細胞に繰り返し実行することで、liveNeighbor
の数が増減します。数が変更した細胞は、EVALUATE
フェーズに設定され、ルールフロー処理の評価段階の推論に含められるようにします。
生存数が判断され、全細胞に設定されると、ルールフロープロセスが終了します。ユーザーが最初に Start をクリックした場合は、その時点でエンジンによりルールフローが再起動します。ユーザーが最初に Next Generation をクリックした場合は、ユーザーが別の世代を要求することができます。
ルール "Calculate …"
rule "Calculate Live" ruleflow-group "calculate" lock-on-active when theCell: Cell( cellState == CellState.LIVE ) Neighbor( cell == theCell, $neighbor : neighbor ) then modify( $neighbor ){ setLiveNeighbors( $neighbor.getLiveNeighbors() + 1 ), setPhase( Phase.EVALUATE ); } end rule "Calculate Dead" ruleflow-group "calculate" lock-on-active when theCell: Cell( cellState == CellState.DEAD ) Neighbor( cell == theCell, $neighbor : neighbor ) then modify( $neighbor ){ setLiveNeighbors( $neighbor.getLiveNeighbors() - 1 ), setPhase( Phase.EVALUATE ); } end