ヒントが間違った手を示す:評価と表示のずれ
遊びをつくる執筆にClaudeを使用しています。

ヒントボタンを押すと、盤面の一マスが光ります。ところがそこは置けない場所だった、あるいは置けても明らかに損な場所だった、という場面を考えてみます。作者としてまず浮かぶのは「評価関数が間違っている」という疑いです。しかし原因が評価側にあるとは限りません。評価は正しい手を選んでいて、それを画面に出す途中で行と列が入れ替わっているだけ、ということもよくあります。 この記事で確かめたいのは「ヒントの誤りが、評価の誤りなのか、表示の誤りなのか」を切り分ける方法です。鍵は、計算結果を画面に出す前の「データとしての手」と、画面に出た結果を別々に取り出し、突き合わせることです。手順を順に示し、最後に短いコードで、評価と表示のあいだに置く検査を作ります。 アソバースの公式作品のうち、ヒントを持つものの仕様も取り上げます。根拠にするのは確認済みの機能だけで、それ以外の細部は「作品を開いて確かめる」項目として残します。
この記事の目次
「間違ったヒント」は三種類に分けられる
ヒントが変だと感じたとき、原因は大きく三つに分かれます。どれなのかを決めないまま直し始めると、別の場所を直して症状だけが隠れることになります。
- 仕様どおり、見え方:最善に見えない手が出る、疑う場所:ヒントの定義、最初の切り分け:ヒントが何を保証するか
- 評価のずれ、見え方:内部で選んだ手そのものが悪い、または不正、疑う場所:評価関数、手番の視点、符号、最初の切り分け:選ばれた手を内部データで検査
- 表示のずれ、見え方:内部の手は正しいが画面の位置が違う、疑う場所:座標変換、盤面反転、更新のタイミング、最初の切り分け:内部の手と画面位置を突き合わせる
一つ目の「仕様どおり」は、見落とされやすい原因です。公式作品の「かたちのあいだ」では、次の一手のヒントは最短とは限らないと案内されています。この作品でヒントの手が遠回りに見えても、それだけでは不具合の証拠になりません。ヒントの約束が「最短」なのか「前進する手」なのかを先に決めておけば、診断の基準が定まります。自作では、ヒントの約束を一文で書き出してから検査に入ってください。
二つ目の「評価のずれ」の典型は、手番の取り違えです。評価を相手の視点で計算してしまうと、自分に不利な手がもっとも高く評価されます。符号の反転も同じ症状を出します。
三つ目の「表示のずれ」は、内部は正しいのに画面が違う状態です。行と列の入れ替え、盤面を反転して見せるときの座標変換の漏れ、ヒントの計算後に盤面が更新されて古い局面の答えを出し続けるケースなどがあります。
公式作品のヒントから読み取れること
ここからは、確認済みの仕様だけをもとに、作品ごとに「どこで評価と表示がつながっているか」を読みます。
潮目のオセロには「理由付きヒント」と「返る石のプレビュー」があり、ヒントと一手戻すはひとり対局のみです。設計から読み取れるのは、一つの手について複数の表示が並ぶということです。ヒントの位置、その理由の文、置いたときに返る石のプレビューは、すべて同じ手から出ているはずです。この三つが互いに食い違えば、どれか一つの表示経路にずれがあると分かります。理由の文の具体的な書かれ方は、作品を開いて確かめてください。
灯りのチェスは、判定エンジンとしてchess.jsを同梱し、取った駒と全棋譜を表示します。黒側のオンライン対局では盤面が反転します。反転表示は、内部の座標と画面の座標が一致しなくなる代表的な場所です。棋譜の表示は、ヒントの手が実際に指されたかを後から確かめる手がかりになります。ヒントの表示形式や、対戦中に使えるかどうかは確認済み事項にないので、作品を開いて確かめます。
月夜の配達には、残り最短距離の表示と、次の一歩を照らすヒント、一歩戻る操作があります。数値の表示と、光る一歩という位置の表示が、同じ経路から出ているのかを検査する価値のある構成です。最短経路上の一歩を進めれば残り最短距離が減ると考えられますが、数え方(小包やバッグの条件を含むか、単位は区間数か)は作品を開いて確かめます。
プリズムの道は、一歩だけのヒントと「戻す」を持ちます。ヒントに従って鏡の向きを一つ切り替え、光の通り道が表示どおりに変わるかを見る、という確認がそのままできます。
かたちのあいだには、一手戻す操作があり、戻しても手数は減りません。この仕様を知らずに「戻したのに手数が合わない」と読むと、表示のずれと誤診します。手数カウンタの意味を先に決めておくことが、検査の前提になります。
三点照合:ヒント・プレビュー・実際の結果
切り分けの基本は、一つの手を三つの経路で見て、すべて一致するか確かめることです。公式作品でも自作ゲームでも、同じ手順で行えます。
準備するものは、記録用の表(紙や表計算ソフトで十分です)、局面を再現する手段(一手戻す操作、または同じ初期配置の再開)、そして左右非対称な局面です。
- 局面を一つ決めて、盤面の配置を書き留めます。数える方向(左上を1行1列とするなど)を先に決めておきます。
- ヒントを出し、光った位置を「何行何列」で記録します。
- プレビューや理由の文があれば、その内容も記録します(返る石の数と位置など)。
- ヒントの位置を、画面上で実際に操作して選びます。
- 操作の結果を記録します。置かれた位置、返った石、手番が変わったか、棋譜や履歴の最後の項目です。
- 一手戻せるなら戻して、次の局面に進みます。
記録の単位は「局面ごとに一行」です。局面は10局面前後を目安にします。盤の隅、端、中央をすべて含めてください。端だけで起きるずれや、対角線上ではずれが隠れる性質があるからです。ひとつの局面を二回以上試す必要はありません。同じ操作で結果が揺れる場合は、評価に乱数や時間制限が入っている可能性があるので、そのときだけ回数を増やします。
結果の読み方は次のとおりです。
- ヒントの位置と、実際に操作して打たれた位置が違う場合は、表示から入力への変換にずれがあります。評価を疑う前に座標変換を見ます。
- 位置は一致するのに、プレビューと実際の結果が違う場合は、プレビューの計算経路が本番の判定と別になっています。
- 三つとも一致するのに手が悪い場合は、評価の問題か、仕様の問題かの区別に移ります。冒頭の表のとおり、ヒントの約束を書いた一文に照らして判断します。
- 理由の文がある場合は、文の中の事実(返る石の数など)が実際の結果と合うかを見ます。文面が自然でも事実が違うなら、文を作る経路にずれがあります。
最小コードで評価と表示のあいだに検査を置く
評価の結果をそのまま画面に出さず、いったん「内部番号で表した手」に落とし、画面用の座標に変換し、さらに逆変換して評価と突き合わせます。三目並べを例にした最小の構成が次のコードです。ファイルに貼ってブラウザで開き、コンソールを見ると動きます。
<script>
const LINES=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
const wins=(b,p)=>LINES.some(l=>l.every(i=>b[i]===p));
// 評価:勝てる手10、相手の勝ちを止める手5、それ以外0、空きでなければ-1
function score(b,i,p){
if(b[i]!=="") return -1;
const q=p==="X"?"O":"X";
const t=b.slice(); t[i]=p; if(wins(t,p)) return 10;
const u=b.slice(); u[i]=q; if(wins(u,q)) return 5;
return 0;
}
const best=(b,p)=>b.map((_,i)=>score(b,i,p))
.reduce((a,s,i,arr)=>s>arr[a]?i:a,0);
// 表示側:内部番号を画面の行・列へ
const toCell=i=>({row:Math.floor(i/3),col:i%3});
const fromCell=({row,col})=>row*3+col;
// 検査:画面に出す座標を内部へ戻し、評価と突き合わせる
function check(b,p){
const shown=toCell(best(b,p));
const idx=fromCell(shown);
const s=score(b,idx,p);
const max=Math.max(...b.map((_,i)=>score(b,i,p)));
return {shown,empty:b[idx]==="",score:s,isMax:s===max};
}
const b=["X","X","","O","O","","","",""];
console.log(check(b,"X"));
</script>盤面は、上段がX・X・空き、中段がO・O・空き、下段がすべて空きです。Xの手番なので、右上に置けば勝ちになります。そこで表示座標は行0・列2、空きで、スコアが最大になるはずです。実際の出力は、読者自身の環境で確かめてください。
このコードの要点は、checkが表示用の座標を作ったあとで、評価の側に戻って検査していることです。表示層が評価層の答えを正しく受け取ったかを、別経路で確かめる形になっています。
検査が働くかどうかも確かめます。fromCellの中身をcol*3+rowに書き換えてみてください。右上は行0・列2なので、逆変換すると番号6(左下)になり、空きではあってもスコアが最大ではなくなります。isMaxが偽になれば、検査が行と列の取り違えを捉えたことになります。これは、わざとバグを入れて検査が反応するかを見る、変異テストと呼ばれる考え方です。
ただし、番号4(中央)や0・8のような対角線上の手だけで試すと、行と列を入れ替えても結果が変わりません。検査用の局面には、必ず対角線上にない手を含めてください。
ずれが出やすい場所と、見逃しやすい罠
最後に、検査を作っても残りやすい落とし穴を整理します。
盤面の反転です。黒側の視点で盤面を反転して見せる場合、内部座標から画面座標へ変換するとき、180度回転が必要になります。ヒントの位置、プレビュー、クリック位置の三つが、すべて同じ変換を通るかを確認してください。一つだけ変換が漏れると、ヒントは正しいのに押した場所が違う、という症状になります。上のコードにflippedの引数を足し、往復して元に戻るかを検査に加えるのが手堅い方法です。
更新のタイミングです。相手が打った直後にヒントを計算すると、古い盤面を参照している場合があります。ヒントを表示する直前に、盤面の状態を表す値(手数や局面の識別子)を検査に含め、表示した時点の局面と同じか確かめます。
視点の取り違えです。評価が「いま手を打つ側」の視点か「黒から見た視点」かを決めておかないと、白番のときだけヒントが逆になります。手番を替えた同一の局面で、スコアの正負が逆転するかを確かめてください。
説明文の事実です。理由付きヒントのように文章を出す場合は、理由の中の数値を別経路で再計算して、実際の結果と一致するかを検査に入れます。文面の自然さは、事実の正しさの証拠になりません。
仕様との混同です。ヒントの約束が「最善」なのか「悪くない手」なのかを決めずに検査すると、仕様どおりの動作を不具合として報告することになります。検査の判定基準は、約束の一文から導いてください。
次の行動
まず自作ゲームのヒントについて、約束を一文で書き出してください。次に、非対称な局面を10個ほど作り、三点照合の表を埋めます。ずれが見つかったら、評価の出力と画面の位置のどちらが先に食い違うかを確かめます。
公式作品では、潮目のオセロの理由付きヒントとプレビュー、プリズムの道の一歩だけのヒントを、同じ手順で照合してみてください。作品を開いて確かめる部分が、そのまま自作ゲームの検査項目の見本になります。

