1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jev にリバーシを打たせてみた ― Deno + Hono で遊んでみる

1
Posted at

はじめに

アップデイティットの毛利です。

みなさん、Jev 触ってみましたか?

ここ数日で実際に遊んでいる方々をSNSで見かけるようになり、流行っているうちに自分でも触ってみることにしました。
こういうものは、説明を読むだけでなく、何か作ってみる方が使いどころを考えやすいですね。

さて、何を作ろうか?、ということで

Jevの安定して高速な応答速度ならCPUの思考ロジックの代替になると思い、
今回は Jev に指し手を決めてもらうリバーシアプリを、作成してみました。

Jev って何?

Jev は、TypeSafe AI が提供する、状況に対して判断を返すことを目的とした AI モデルです。

一般的なLLMが出力する文章生成の方向性ではなく、判断材料となる state と質問を与え、プログラムで利用できる形式の答えを受け取ります。
選択肢から一つを選ぶ Choice、基準に沿って評価する Score、ある命題が真である確率を返す Noul が用意されています。1

今回使うのは、このうちの Choice です。

リバーシで現在打てる場所の候補は、プログラムが判定できるため、
例えば候補が4つあったら、その4つから1つ選んでもらう、確かにJevが得意そうな予感がしますね。

詳しい仕組みについては、公式ドキュメントを参照してください。
今回は、実際にアプリを作って遊んだところを中心に紹介します。

動機、環境など

この記事のゴール

  • Jev にリバーシの盤面を渡し、次の一手を選んでもらう。
  • 実際に対戦して、指し手・応答速度・利用料金の感触を確認する。

最強のリバーシ AI を作ることが目的ではなく、
まずは API を使って、使用感を見てみる、というところが今回のゴールです。

想定読者

とにかく話題の Jev が気になる方を想定します。

最近は、リファレンスサイトやSDKの使い方を熟読しなくても、
プロトタイプを作るところまですっ飛ばせるので、とても良い時代ですね。

環境

用途 使用したもの
実行環境 Deno
Web フレームワーク Hono
開発ツール Codex (Luna Max)
対戦中の指し手の選択 Jev

基本的に開発作業は Codex に一任します(もちろん壁打ちはしますが)。

また、Jev とは別に、API を使わずプログラムで指し手を計算する CPU ロジックも用意します。
こちらは、主に比較検討用ですね。

作業

1. Codex でフロントエンドを爆速開発する

前述のとおり、フロントエンドを含む実装面を、すべて Codex に一任します。
最近のモデルは、雑なプロンプトでも(欲を言わなければ)動作する初版までたどり着くため、まずは作りきってもらいましょう。

フロントエンドが完成するまで

最初は、以下のプロンプトを与えました。

# TODO

Jev という AI を使ってリバーシアプリを開発したい。

## 要件

- 言語(ランタイム)はDeno + TypeScript
    - フレームワークは hono を使用 
- Jev のリファレンスは https://docs.typesafe.ai/introduction
    - APIキーを用いて実行する
- Deno+hono を使ったHTTPサーバーとして動作する
- リバーシの仕様の補足
    - 先行後行は「スタート」を押したら自動で判定する
    - CPUは、Jev に盤面の情報を渡し、「choice」モードで、次に最も有効な手を選ばせる。
    - choiceで判定された目を返し、ユーザーのターンに返却する
    - ユーザー・CPUともに「指せる目」がない場合はSKIP演出を設けてターンを渡す

待つこと20分程度、出来上がったものには次の問題がありました。

  • コンソールや、ログファイルに何1つ書き出さない。
  • .env にAPIの記述を指定しているのに、deno task startで自動でdotenvを読み込まない。
  • UI上から、CPUがJevを使用中か未使用かの表記されない。
  • フォントサイズの暗黙ルールが英語圏に引っ張られており、h1はデカすぎ、div,spanは小さすぎる。
  • リバーシの盤面のサイズが可変値になっており、石を配置するたびにグニャグニャになる。
  • Jev の公式SDKを使用していない
    • 与えたURLにはSDKについて直接の言及がなかったためと思われる

逆に「よくもまぁこんな雑なプロンプトでここまで察して作れたな・・」という感じです。
1つずつ、修正の壁打ちを行いながら、一旦の完成にまでこぎつけました。

a37ac550f002-20260918.png

盤面だけでなく、先手を選ぶ操作、スコア、CPU の判断を表示する領域まで、まとめて作ってもらっています。

特にデザインを考えて作り込む作業は、多少慣れていても時間を取られる作業なので、
(今回は画面を作り込むことが本題ではないため)このあたりを任せられるのは非常に助かりました。

2. 動作確認用に、普通の CPU も用意する

Jev と接続する部分とは別に、普通にプログラムで計算して指し手を決める CPU ロジックも実装させました。
というよりも、勝手に Codex が生やしていました(前述のプロンプトの内容しか与えてなかったのですが)

以後、本記事ではこちらを「通常CPU」と呼びます。

最初から Jev だけを相手にすると、何かおかしかったときに、画面の問題なのか、ゲームの処理なのか、API とのやり取りなのか、切り分ける場所が増えてしまいます。
そのため、完全ローカルでも動作するようにしておけば、盤面や手番の処理を確認しやすくなります。

少し遠回りに見えて、こういうものがあると便利なので、どうやらCodexがよしなにしてくれたようです。

なお、どういったロジックで指し手を決めるのかというと

  • 合法手を全列挙し、各手を仮適用して評価する。
  • 仮適用後の位置評価を3倍、反転数を2倍、自分と相手の石数差を加点し、相手の合法手数を4倍して減点する。角ならさらに500点を加える。
  • 評価値が最大の手を選び、同点なら座標順で決定する。

と、結構合理的なロジックが組まれていました。(人間が開発作業していたらランダム判定にしてお茶を濁したかも・・)

また実装上は、APIキーが未設定の場合や、Jev のリクエストに失敗した場合にも、このローカルポリシーへフォールバックします。返却値の source は fallback になり、画面では NORMAL LOGIC と表示されます。

(そして、この通常CPU が、後で思わぬ結果を生み出すとは..)

3. Jev に盤面を渡し、指し手を選んでもらう

盤面のバイアス

今回の工夫として、state.positionBias に各マスのバイアス情報(評価マップ)を与えています。
いわゆる「カドとカドの1つ飛びのマスを優先して狙え」などの優先度の情報です。

Jev の state は、評価対象や、その判断に必要な情報を渡す場所で、文字列だけでなく、JSON のオブジェクトや配列も渡せます。2

前述の通常CPUのロジックにも、同じような機能が含まれていますね。それをJevが判断できるように情報を与えます。

毎回の INPUT に、盤面の「白・黒・空」を与える

盤面は、一手ごとに変わります。
そのため、Jev を呼び出すたびに、各マスが「白」「黒」「空」のどれなのかを渡します。

もちろん無駄に画像を読ませるのではなく、boardToGlyphs(board) で変換した8×8の二次元配列を state.board に渡す形です。黒は B、白は W、空きマスは . で表し、その対応関係は state.boardLegend にも入れています。

「指せる手」を Choice の候補にする

あらかじめ次に指せる手がどれかは判定できるため、絞ったうえで候補にし、その中から一つ選んでもらいます。
実装では getLegalMoves で求めた合法手から criteria を毎回生成し、それを questions.move の choice に渡しています。

Choice は、定義した候補から選択結果を返すための質問形式なので、この部分に利用しています。3

実装コードの中心部分は、次のようになっています。board と legalMoves は現在の盤面から毎回生成されるため、固定の行文字列や、あらかじめ書いた候補を送っているわけではありません。

以下は 実装を読みやすく抜粋したものです。positionBias.values は実際には POSITION_WEIGHTS の8×8配列から設定されます。

const criteria = Object.fromEntries(
  legalMoves.map((move) => [
    move.coordinate,
    `合法手。${move.flips} 個の石を反転し、座標は row=${move.row}, col=${move.col}。`,
  ]),
);

const request = {
  state: {
    game: "reversi",
    board: boardToGlyphs(board),
    boardLegend: { B: "black", W: "white", ".": "empty" },
    cpuColor: color,
    opponentColor: color === "black" ? "white" : "black",
    positionBias: {
      description:
        "固定の基本位置評価値。数値が高いほど基本的に有利なマスとして扱い、合法手の選択時の補助バイアスにする。",
      coordinateSystem:
        "valuesは行1〜8、列a〜hの順。座標は列の英字+行番号(例: a1)。",
      columns: ["a", "b", "c", "d", "e", "f", "g", "h"],
      rows: [1, 2, 3, 4, 5, 6, 7, 8],
      values: POSITION_WEIGHTS.map((row) => [...row]),
    },
    legalMoves: legalMoves.map((move) => ({
      coordinate: move.coordinate,
      flips: move.flips,
    })),
    instruction:
      "Only choose one coordinate from legalMoves. Prefer the move most likely to win.",
  },
  model: "jev-latest",
  questions: {
    move: choice(
      "リバーシのCPUとして、現在の盤面で最も有効な合法手を1つ選んでください。必ず候補の座標を1つだけ返してください。",
      criteria,
    ),
  },
};

初期盤面で黒が打つ場合、legalMoves の座標は d3、c4、f5、e6 です。実際の criteria のキーもこのような小文字の座標になり、説明文には反転数と、0始まりの内部 row / col が入ります。board は文字列8個を並べた配列ではなく、B / W / . を要素に持つ8×8の配列です。

公式 API では、質問IDと同じキーで答えが返ってくるため、この実装では answers.move.choice が選ばれた場所になります。返された座標は正規化したうえで合法手と照合し、合法手でなければエラーとしてローカルのフォールバックに切り替えます。4

なお、Choice の回答には各候補の確率や confidence が含まれることがあります。実装は confidence と probabilities を受け取りますが、confidence をそのままリバーシの勝率と読むことはできません。返却形式が整っていることと、選んだ一手が強いことも、また別です。3

いざ対戦

ということで、実際に対戦してみます。

5068d435f5d8-20260918.png

……弱い。

とりあえずアプリとしては動いていますし、Jev に一手を選んでもらうこともできています。
ただ、対戦相手として見ると、動作確認用に用意した通常CPU の方が普通に頭が良いです。

社内でも遊んでもらったのですが、やっぱり弱い...

ちょっと想定と違いました笑。

所感

通常CPUの方が頭が良い

今回の開発のレベル感では、通常CPU の方が強くなってしまいました。

ただ、これだけで「Jev は使えない」とするのも違うと思っていて、Jev に与えるプロンプトや、判断材料の整理が弱い可能性は十分にあります。

例えば、次の改善の余地があると思っています。

  • そもそもJev自身がリバーシのマス目のバイアス情報を知ってるのでは?問題
    • すでに知識として持っているとしたら不要な情報だったのでは
  • 2手、3手先を予測することを踏まえた指示文になっていないでは?問題
    • もう少し複雑な判断をするようにプロンプトを改善したらどうか

公式でも、Jev は素早く判断できる範囲の質問を得意とし、複雑な判断は小さく分けるよう案内されていますが、
もう少し判断の視野を広げてやっても良いのではと思っています。

少なくとも、初版バージョンの実装では 「期待したほど強くなかった」 と受け止めています。

レスポンスは確かに速い

一方で、応答速度は好印象でした。前情報通り。

普段使っている一般的な LLM の API と比べると、Jev はレスポンスが速く感じます。
一手ずつ判断を返してもらう今回のような使い方では、この軽さは嬉しいところです。

デバッグログ上のAPIのレスポンスタイムも、平均300msを記録していましたので、
安定して速い応答がもらえるという前情報通りでありますね。

なお、SNSを見ると、日本から通信している分の距離の遅延の方が目立つという感想を投稿されている方もいたため、Jevと物理的に近ければもっと速度の恩恵を受けられるのかもしれませんね。

利用料金も安い

TypeSafe Consle 画面にある Usage を確認したところ、表示は < $0.01 でした。

数回対戦して、たかだか100回も満たない実行回数・トークン量程度では、正しい金額にならないということでしょう。

SKIPなしで盤面が最後まで埋まる場合、初期の4石を除く60手を交互に打つため、CPUがJevを呼び出す回数は30回です。
1コールあたりの入力を1,000トークン、Jevの入力単価を100万トークンあたり $0.042 として計算します。5

項目 計算
1コール 1,000トークン = $0.000042
30コール(1対局) 30,000トークン = $0.00126
日本円換算(1ドル=150円) 約0.189円

出力トークンは無料なので、この前提では1対局あたり約0.19円です。途中で両者が打てなくなった場合は、Jevの呼び出し回数もこれより少なくなります。

早期決着なども考慮すると $5 で 約4000対局遊べるようです。破格ですね。

おわりに

今回は、Deno + Hono をベースに、Codex でリバーシアプリを作り、Jev に指し手を選んでもらいました。
ちょっと残念な性能ですが、まだまだ改善の余地があるので、もうちょっと取り組んで、せめて通常CPUよりは強くなるようにしてみたいと思います。


弊社では、ドキュメントの作成・管理・レビューをアプリケーションとAIの両方から支援する「crossnote」を開発・提供しています。

ドキュメント業務の効率化や AI の活用にご興味がありましたら、弊社お問い合わせ窓口より、お気軽にお問い合わせください。

  1. TypeSafe AI — Introduction。Jev の位置づけ、質問形式、複雑な判断を分割する考え方について。2026年9月18日確認。 ↩

  2. TypeSafe AI — State。state に渡せる形式と、判断材料・質問の分離について。2026年9月18日確認。 ↩

  3. TypeSafe AI — Choice。候補の選択、probabilities、confidence の意味について。これらをゲームの勝率と解釈できる仕様ではありません。2026年9月18日確認。 ↩ ↩2

  4. TypeSafe AI — API reference。systemOne のリクエスト・レスポンス構造と、質問IDに対応する回答について。掲載したコード抜粋は、この形式でリクエストを組み立てています。2026年9月18日確認。 ↩

  5. TypeSafe AI — Models。Jev 1.13 の公式価格は入力 100 万トークンあたり $0.042、出力は無料。料金表のモデル名は確認時点のもので、今回の対戦時に使用された固定バージョンを示すものではありません。2026年9月18日確認。 ↩

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?