2024年3月1日金曜日

対話グリッドで数独を実装する

Oracle APEXの対話グリッドで数独を実装してみました。問題を入れると解答を見つけるというものではなく対話グリッドで9x9のマス目を作成して、そのマス目に入っている数値が数独のルールに従っているかどうか、検証を行なうアプリケーションです。

作成したアプリケーションは以下のように動作します。


以下より、アプリケーションの作成手順を紹介します。

最初に以下のパッケージSUDOKUを作成します。数独の検証ロジックを実装しています。また、数独の9x9のマトリックスの数値はパッケージ変数G_MATRIX: (コロン)で区切った文字列として保存します。G_ERRORSには重複している値を保存します。

アプリケーション作成ウィザードを起動します。

アプリケーションの名前数独とします。デフォルトで追加されているホーム・ページを削除し、代わりに対話グリッドのページを追加します。


対話グリッドの名前数独、ソースとしてSQL問合せを選択し以下のSELECT文を記述します。編集を許可を選択します。



アプリケーションの作成をクリックして、これから開発するアプリケーションを作成します。


ページ・デザイナ対話グリッドのページを開きます。

アプリケーション作成ウィザードが対話グリッドの列IDを主キーとして認識していません。列IDを選択し、識別タイプ非表示ソース主キーオンに変更します。


FUを選択し識別タイプ非表示にし、設定保護された値オフにします。この列は変更のない対話グリッドの行をサーバーに送信するために、ページの送信前に動的アクションから値を変更するために使用します。利用者はこの列を見る必要がないため非表示にし、動的アクションからの更新を許可するために保護された値オフにしています。


対話グリッドのプロパティの属性を開きます。

編集実行可能な操作から行の追加行の削除外します。可能な操作は行の更新に限ります。

ツールバーより検索列の選択検索フィールド、それと保存ボタンを外します。対話グリッドの保存からは、呼び出される検証プロセス編集可能リージョンとして(保存ボタンのある対話グリッドである)数独が紐づけられているものに限られます。また、呼び出される検証プロセスは、変更された行毎に複数回呼び出されます。今回は対話グリッドには紐づかず、送信時に一度だけ呼び出される検証プロセスの実行が必要なため、別にボタンを作成してページの送信を行います。


対話グリッドをJavaScriptから扱うため、詳細静的IDとしてSUDOKUを設定します。


9x9の配列に入力した数値を保存するページ・アイテムを作成します。

識別名前P1_MATRIXタイプテキスト・フィールドです。ラベル配列とします。


ページ・アイテムP1_MATRIXを初期化するプロセスを作成します。

レンダリング前ヘッダーの前プロセスを作成します。識別名前P1_MATRIXの初期化タイプコードの実行とします。ソースPL/SQLコードとして以下を記述します。



検証を実行するボタンを作成します。

作成したボタンはページ・アイテムP1_MATRIXの下に配置します。識別ボタン名VALIDATEラベル検証とします。外観ホットオンテンプレート・オプションWidthStretchSpacing BottomLargeを選択します。

動作アクションはデフォルトのページの送信とします。


左ペインでプロセス・ビューを開き、検証を作成します。

識別名前G_MATRIXの初期化とします。検証編集可能リージョンは未指定(- 選択 -のまま)にし、ページ送信時に1回だけ実行するようにします。タイプを選び、PL/SQL式として以下を記述します。

sudoku.initialize(:P1_MATRIX)

パッケージ変数G_MATRIXにページ・アイテムP1_MATRIXの値を設定しています。

ファンクションsudoku.initializeは検証として実行されますが、必ずtrueを返します。そのため検証に失敗することはありません。しかし、エラー・メッセージは必須であるため適当な値(今回は . ピリオド)を設定しておきます。

サーバー側の条件ボタン押下時VALIDATEを設定します。


検証常に実行というフラグがあります。今回はこれをオフにしています。


この設定はボタンの動作検証の実行に対応しています。常に実行オンのときは、ボタンの検証の実行オフであっても、検証が実行されます。


続いて実行される検証を作成します。

識別名前G_MATRIXを更新とします。対話グリッドで変更された行を受信し、パッケージ変数G_MATRIXを更新します。結果として数独の配列の値が最新の状態になります。この検証も常に成功します。

検証編集可能リージョン数独とします。この検証は対話グリッドで変更された行の数だけ繰り返し実行されます。タイプとしてファンクション本体(ブールを返す)を選択し、PL/SQLファンクション本体として以下を記述します。

エラー・メッセージおよびサーバー側の条件は、先ほどの検証と同様に設定します。


続いて実行される検証を作成します。

識別名前数独の検証とします。検証編集可能リージョンは未指定(- 選択 -のまま)にし、ページ送信時に1回だけ実行するようにします。タイプを選び、PL/SQL式として以下を記述します。

sudoku.validate_all()

数独の配列を検証します。検証結果はパッケージ変数G_ERRORSに保存し、このファンクション自体はつねにtrueを返します。

エラー・メッセージおよびサーバー側の条件は、先ほどの検証と同様に設定します。


検証結果を画面に反映させる検証を作成します。

識別名前C1の検証とします。検証編集可能リージョンとして数独を設定します。この検証は対話グリッドが送信する行ごとに実行されます。タイプを選択し、PL/SQL式として以下を記述します。

sudoku.is_valid(1, :ID)

第1引数列の位置第2引数行の位置です。その位置にある値が数独のルールに違反しているときにfalseが返されます。

この検証結果は画面に表示します。エラー・メッセージ重複しています。とします。表示位置フィールド上でインライン表示関連付けられた列C1を設定します。エラー・メッセージは、その対話グリッド上のフィールドに表示されます。

サーバー側の条件ボタン押下時VALIDATEを設定します。


検証サーバー側の条件実行スコープの設定があります。作成済および変更済の行またはすべての送信済の行の2通りを選ぶことができますが、リージョンが対話グリッドの場合、対話グリッド自体が作成済および変更済の行しか送信しないため、この設定に意味はありません。このような設定になっている理由については、下記のFR-2608に説明があります。

エンハンスメント・リクエストがAPEX IdeasのFR-2608 Interactive Grid: Execution Scope = "All Submitted Rows" should submit all rows, not just new/modified rows として上がっており、ステータスが将来実装予定(ROADMAP)になっています。


作成した検証を重複させ、検証C2の検証を作成します。名前PL/SQL式関連付けられた列1が割り当たっている部分を2に変更します。


同様の作業を列C9までを対象として繰り返します。

最終的に列の検証が9つ、全部で12の検証が作成されます。


プロセス数独 - 対話グリッド・データの保存を選択し、対話グリッドのデータをページ・アイテムP1_MATRIXに保存するように変更します。

識別タイプコードの実行に変更し、編集可能リージョンを未選択(- 選択 -に戻す)にします。ソースPL/SQLコードとして以下を記述します。

sudoku.save(:P1_MATRIX);

成功メッセージ検証できました。とします。サーバー側の条件ボタン押下時VALIDATEを設定します。


ここで一旦、アプリケーションの動作を確認してみます。

同じ列で数値が重複している場合は、両方のフィールドともにエラーが通知されます。


しかし行が異なる場合は、エラーが通知されない場合があります。


数独の配列G_MATRIXは初期のデータをページ・アイテムP1_MATRIXから取り出し、対話グリッドから送信された値で更新しているため、9x9の配列のデータをすべて保持しています。しかし、検証C1の検証からC9の検証までは、対話グリッドが送信する行、つまり変更された行だけを対象に検証が呼び出されます。結果として、エラー自体は検出されていても(パッケージ変数G_ERRORSには保存されている)、変更がされていない行ではエラーが通知されません。

検証を実行するためには、変更の有無にかかわらずすべての行をサーバーに送信する必要があります。

Oracle Corporationに所属しており、対話グリッドを開発したJohn Snydersさんが、彼のブログで対話グリッドの検証について記事を書かれています。

Interactive Grid Validation

この記事の中で紹介されているAPEXアプリケーションIG Validateの、EMP Validate 6(ページ番号)にページ送信前に変更のされていない行を見つけて非表示の列を更新する(ことにより送信対象にする)JavaScriptの実装が含まれています。

この実装を導入します。

左ペインで動的アクション・ビューを開き、動的アクションを作成します。

識別名前IGの全行を更新するとします。タイミングイベントカスタムを選択し、カスタム・イベントapexpagesubmitを設定します。選択タイプJavaScript式を選択し、JavaScript式としてapex.gPageContext$を設定します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下を記述します。



以上でアプリケーションは完成です。今度はすべての行が送信されているため、数値が重複しているフィールドがすべてエラーになります。


対話グリッドの検証については、Matthew Mulvaneyさんによる以下のブログ記事も参考になります。

Interactive Grid duplicate values – Learn How to prevent a common problem using a Zero-JavaScript approach

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sudoku-validate.zip

対話グリッドのみで実装した版のエクスポートは以下です。
https://github.com/ujnak/apexapps/blob/master/exports/sudoku-validate2.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。