2025年2月14日金曜日

OpenAI o3-mini-highにレトロゲームを作ってもらってOracle APEXのページに組み込む

OpenAIのo3-mini-highにインベーダーゲーム風のレトロゲームを作ってもらって、Oracle APEXのアプリケーションに組み込んでみます。

最初に与えたプロンプトです。

「JavaScriptでインベーダー・ゲームを書いて」

単一のHTMLページが生成されます。最後に以下のようにメッセージが返されています。

このコードをローカルに保存し、ブラウザで開くとシンプルなインベーダー・ゲームが動作します。

※このサンプルは基本的な実装例ですので、衝突判定の精度向上やエフェクトの追加など、さらなる拡張が可能です。

最初にこの生成されたコードをAPEXに組み込んでみます。場合によってReactのコードが生成されることがあります。Oracle APEXにReactを組み込むのはほぼできないので、その場合はプロンプトにVanilla JavaScriptと明示すると、フレームワークを使わないコードが生成されます。


空のAPEXアプリケーションを作成します。名前Retro Gameとします。


デフォルトで作成されるホーム・ページに組み込みます。

ページ・デザイナホーム・ページを開きます。


生成されたコードのstyleタグからbodyの指定を除いた記述を、ページ・プロパティCSSインラインに転記します。



scriptタグに記述されたJavaScriptのコードをコピーします。


ページ・プロパティJavaScriptページ・ロード時に実行に転記します。


静的コンテンツのリージョンを作成し、生成されたコードに含まれているキャンバス要素をソースHTMLコードとして記述します。

<canvas id="gameCanvas" width="800" height="600"></canvas>


以上で組み込みは完了です。ページを実行して遊びます。


ゲームに一時停止再開のボタンを追加します。

「一時停止と再開のボタンを追加してください。」

一時停止と再開のボタンが追加されたコードが生成されました。


APEXアプリケーションに空白ページを作成し、先ほどと同じ手順でstyleタグとscriptタグの記述をページに転記します。

静的コンテンツのリージョンに、ボタンが追加された以下のHTMLソースを記述します。
  <div id="controls">
    <button id="pauseButton">一時停止</button>
    <button id="resumeButton">再開</button>
  </div>
  <canvas id="gameCanvas" width="800" height="600"></canvas>

ページを実行して遊んでみます。動きはしますが、一時停止は効きません。これはボタンをクリックしたときに、APEXのデフォルトの動作であるページの送信が実行されているためです。


ボタンのクリック時に後続の処理をキャンセルするように指示します。

「ボタンのクリック時にpreventDefaultを呼び出し、後続のイベント処理をキャンセルしてください。」

preventDefault()を呼び出すコードが生成されました。


APEXアプリケーションに空白ページを作成し、先ほどと同じ手順でstyleタグとscriptタグの記述をページに転記します。

ページを実行して遊んでみます。今度は一時停止再開が効きます。


データベースとレトロ・ゲームを連携するために、一時停止したときの状態をJSONドキュメントとして出力するファンクションと、そのJSONドキュメントを受け取ってゲームを再開するファンクションを作成してもらいます。

「一時停止したときの状態をJSONドキュメントとして出力するファンクションと、そのJSONドキュメントを受け取ってゲームを再開するファンクションを作成してください。」

ファンクションexportGameState()とimportGameState(jsonDocument)のふたつのファンクションが生成され、状態出力状態読込というボタンが追加されたコードが生成されました。生成されたコードはこちらです。


APEXアプリケーションに空白ページを作成し、先ほどと同じ手順でstyleタグとscriptタグの記述をページに転記します。

静的コンテンツのリージョンには、ボタンとテキスト領域が追加された以下のHTMLソースを記述します。
  <div id="controls">
    <button id="pauseButton">一時停止</button>
    <button id="resumeButton">再開</button>
    <button id="exportButton">状態出力</button>
    <button id="importButton">状態読込</button>
  </div>
  <textarea id="stateOutput" placeholder="ここにJSON状態を貼り付けて読込"></textarea>
  <canvas id="gameCanvas" width="800" height="600"></canvas>

ページを実行して遊んでみます。状態出力状態読込の両方とも、きちんと動作するようです。


最後にデータベースと連携させます。

クイックSQLの以下のモデルより、ゲームの状態を保存する表EBAJ_RETRO_GAME_STATEを作成します。
# prefix: ebaj
retro_game_state
    state_name vc80 /nn /unique
    state json
レビューおよび実行をクリックした後は、実行即時実行のボタンをクリックして表を作成するところまで進みます。


EBAJ_RETRO_GAME_STATEが作成されます。


これからは、通常のAPEXのアプケーション作成作業を行います。

ページ4をコピーしてページ5を作成し、データベースへの連携を追加します。


ページ4からページ5を作成します。


新規ナビゲーション・メニュー・エントリの作成を選択します。


ページのコピーを実行します。


ページのコピーが作成されました。


APEXのアプリケーションらしくするため、および、ボタンの動作をAPEXアクションとして定義するために、Gameのリージョンに作成されているボタンやテキスト領域に代えて、APEXのボタンなどを作成します。

ボタンなどを配置するリージョンを静的コンテンツとして作成します。APEXアクションのコンテキストを作成するため、静的IDとしてCONTROLSを設定します。


ボタンPAUSE一時停止)、RESUME再開)、EXPORT状態出力)、IMPORT状態読込)を作成します。これらの動作アクション動的アクションで定義を選択し、詳細カスタム属性としてdata-action="<ボタン名>"を設定します。


ボタンRESETは表EBAJ_RETRO_GAME_STATEの内容を全削除して、初期状態に戻すために使用します。このボタンだけは動作アクションページの送信です。


JSONドキュメントとして出力した状態はデータベースに保存します。データベースへの状態の保存および取り出しを実行するにあたって、状態名の指定および選択にコンボボックスを使うことにします。

先にコンボボックスの手動入力アイテムを作成します。識別名前P5_STATE_NAME_NEWタイプ非表示です。コンボボックスの手動入力アイテムなので、設定保護された値オフにします。

セッション・ステートデータ型VARCHAR2ストレージリクエストごと(メモリーのみ)とします。


コンボボックス名前P5_STATE_NAMEとします。手動入力アイテムとして作成済みのP5_STATE_NAME_NEWを指定します。

LOVタイプSQL問合せを選択し、SQL問合せとして以下を記述します。

select state_name d from ebaj_retro_game_state order by id asc

セッション・ステートデータ型VARCHAR2ストレージリクエストごと(メモリーのみ)です。


リージョンGameソースHTMLコードより、ボタンやテキスト領域の要素を除きます。

<canvas id="gameCanvas" width="800" height="600"></canvas>


ブラウザのJavaScriptから呼び出す、データベースにゲームの状態を保存するプロセスをAjaxコールバックとして作成します。名前EXPORTソースPL/SQLコードとして以下を記述します。


データベースからゲームの状態を取り出すプロセスをAjaxコールバックとして作成します。名前IMPORTソースPL/SQLコードとして以下を記述します。



ボタンRESETを押したときに呼び出されるプロセスとしてRESETを作成します。

ソースPL/SQLコードに以下を記述します。

delete from ebaj_retro_game_state;

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


ボタンなどのコンポーネントをAPEX由来のものに置き換えたので、ページ・プロパティJavaScriptページ・ロード時に実行こちらのコードに置き換えます。

とりあえず一通りは完成しました。ページを実行して遊んでみます。

ボタン状態出力および状態入力でデータベースへの保存と取り出しはできていることは確認できました。しかし、キャンバス要素外でのキーボード・イベントでミサイルとかが発射されたりします。また逆にボタンにフォーカスが当たっていると、キャンバス上でのキーボード入力に反応します。


以下のプロンプトを与えて、問題点を修正します。

「Canvas上にポインタが載っているときだけ、キーボード操作を有効にしてください。
また、mouseenterの際にcanvasにフォーカスをセットしてください。」


不具合を修正したコードに置き換えます。

リージョンGameのHTMLコードに含まれるキャンバス要素にフォーカスを当てるため、tabindex="0"が追加されています。

<canvas id="gameCanvas" width="800" height="600" tabindex="0"></canvas>


不具合を修正したJavaScriptコードに含まれるボタンの処理をする部分を、再度APEXと連携するコードに置き換えます。置き換えたコードで、ページ・プロパティJavaScriptページ・ロード時に実行を置き換えます。


ページを実行し遊んでみます。先ほどの不具合は修正されているようです。


以上でAPEXアプリケーションは完成です。

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

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