ヒントの作り方:答えを全部出さずに一歩だけ示す
遊びをつくる執筆にClaudeを使用しています。

迷路パズルの途中で、プレイヤーが「ヒント」を押す場面を考えてみます。画面に解答の経路が最後まで線で描かれたら、パズルはそこで終わってしまいます。「そっちに進めばよい」という矢印が一つだけ出れば、次の判断はプレイヤーに残ります。どちらも同じ「正解」を知っているのに、渡し方で体験が変わります。 この記事が確かめるのは、その渡し方がプログラムでどう作れるかです。核になるのは、最適解を最後まで求めたうえで、最初の一手だけを取り出す処理です。解を全部持っていながら、見せるのは先頭の一歩だけにします。仕組みは短いコードで書けるので、後半で動かせる形にします。 公式作品の中にもヒントを持つものがあります。確認済みの機能説明だけを手がかりに、設計から読み取れることを整理します。内部の実装は公開されていないので、確かめられない部分は「作品を開いて確かめる」と書きます。
「答えを渡す」と「一歩を示す」は何が違うのか
ヒントには、情報量の違う段階があります。
- 解の存在だけを教える(「この局面は解けます」)
- 残りの距離を教える(「あと何手」)
- 次の一歩を教える(「右へ」)
- 解の全体を教える(経路を最後まで表示する)
最適解を求めるアルゴリズムは、本来4番まで出せます。3番は、4番の出力の先頭だけを使うことで作れます。2番も、同じ計算の途中経過から得られます。段階の違いは探索の違いではなく、出力のどこまでを見せるかの違いです。
公式作品の説明にも、この段階の違いが表れています。月夜の配達には「残り最短距離」と「次の一歩を照らすヒント」があります。かたちのあいだには「次の一手のヒント」があります。プリズムの道には「一歩だけのヒント」があります。どれも解の全体ではなく、一歩を示す形です。
距離マップから一歩だけ取り出す
迷路のようにゴールが決まっている問題では、「ゴールからの距離マップ」を使うと一歩のヒントが簡単に作れます。手順は次の2段階です。
- ゴールから幅優先探索をして、各マスに「ゴールまでの残り歩数」を書き込みます。幅優先探索とは、近いマスから順に外へ広げていく探索のことです。
- 現在地の隣のマスのうち、残り歩数がいちばん小さいマスへの一歩を返します。
ゴールから計算する点が肝心です。スタートから探索すると、現在地が変わるたびにやり直しになります。ゴールからの距離マップは一度作れば、どのマスにいるプレイヤーにも使い回せます。
次のコードは、HTMLファイルとして保存すればそのまま動く最小構成です。外部ライブラリは使いません。
<pre id="out"></pre>
<script>
const maze = [
"S..#....",
".#.#.##.",
".#...#..",
".####.#.",
"......#G",
];
const H = maze.length, W = maze[0].length;
const DIRS = [[0,-1,"上"],[0,1,"下"],[-1,0,"左"],[1,0,"右"]];
const find = ch => {
for (let y = 0; y < H; y++) {
const x = maze[y].indexOf(ch);
if (x >= 0) return [x, y];
}
};
const open = (x, y) =>
x >= 0 && y >= 0 && x < W && y < H && maze[y][x] !== "#";
// ゴールから幅優先探索して、各マスの「残り歩数」を作る
function distanceMap(goal) {
const d = Array.from({ length: H }, () => Array(W).fill(Infinity));
const queue = [goal];
d[goal[1]][goal[0]] = 0;
for (let i = 0; i < queue.length; i++) {
const [x, y] = queue[i];
for (const [dx, dy] of DIRS) {
const nx = x + dx, ny = y + dy;
if (open(nx, ny) && d[ny][nx] === Infinity) {
d[ny][nx] = d[y][x] + 1;
queue.push([nx, ny]);
}
}
}
return d;
}
// 現在地から、残り歩数が最も小さい隣のマスへの一歩だけを返す
function hint(pos, d) {
let best = null;
for (const [dx, dy, name] of DIRS) {
const nx = pos[0] + dx, ny = pos[1] + dy;
if (open(nx, ny) && (!best || d[ny][nx] < best.cost)) {
best = { name, cost: d[ny][nx], to: [nx, ny] };
}
}
return best;
}
const goal = find("G"), d = distanceMap(goal);
const pos = find("S");
const h = hint(pos, d);
document.getElementById("out").textContent =
"残り " + d[pos[1]][pos[0]] + " 歩 / ヒント: " + h.name + " へ一歩";
</script>distanceMap が「最適解を求める」部分です。hint は、その結果から先頭の一歩だけを取り出す部分です。経路全体を配列にして返すことも、hint を繰り返し呼ぶだけで簡単にできます。しかし画面に出すのは一歩分にとどめます。出す量は表示側で決めるものです。
コードを動かして確かめられることがあります。
- 迷路の文字列を書き換えて、行き止まりの近くにある pos を指定したとき、ヒントが行き止まりから離れる向きになるか確かめます。
- maze の G を壁で囲んで到達不能にしたとき、距離が Infinity のまま残ります。この場合 hint は意味のない向きを返しかねません。「ヒントを出せない」状態の扱いを自分で決めておきます。
公式作品のヒントから読み取れること
迷路以外のゲームでも、「状態」を一つの点と見なせば同じ考え方が使えます。スライドパズルなら、盤面の配置が一つの点で、一手動かすことが隣の点への移動です。ゴールは「数字がそろった配置」です。次の表は、確認済みの機能説明から読み取れる範囲を整理したものです。
- 月夜の配達、確認済みのヒントの形:残り最短距離と、次の一歩を照らすヒント、設計から読み取れること:距離の表示と一歩の提示が別の機能として用意されている、確かめ方:ヒントを使わず残り最短距離だけを見て進めるかを試す
- プリズムの道、確認済みのヒントの形:一歩だけのヒント、設計から読み取れること:解の全体ではなく、一歩単位で示す設計です、確かめ方:同じステージでヒントを連続して使い、毎回一歩分だけ進むかを見る
- かたちのあいだ、確認済みのヒントの形:次の一手のヒント(最短とは限らない)、設計から読み取れること:最短保証より、次の一手を示すことを優先している可能性がある、確かめ方:同じ配置で手数を数え、ヒントに従った場合との差を自分で記録する
- 潮目のオセロ、確認済みのヒントの形:理由付きヒント(ひとり対局のみ)、設計から読み取れること:一手に理由が添えられ、オンライン対局には出さない、確かめ方:ひとり対局とオンライン対局の画面を見比べる
- 灯りのチェス、確認済みのヒントの形:ヒント、一手戻す、設計から読み取れること:対局が主体の作品で、ヒントは補助機能として置かれている、確かめ方:ヒントの出る場面と、CPUの応手の速さを見比べる
かたちのあいだは、4×4でも遊べます。参考として、3×3のスライドパズルの到達可能な配置は 9!÷2 で181,440通りです。4×4では16!÷2で約10兆通りになり、桁が大きく変わります。ヒントが最短とは限らないと明記されているのが、この規模の差によるものかどうかは、案内からは判断できません。この部分は作品を開いて確かめるしかありません。
オセロやチェスは事情が異なります。終局まで読み切れないため、迷路のような「ゴールまでの最短距離」は使えません。一般には、局面ごとに評価値をつけ、数手先まで読んで最も良い手を選びます。灯りのチェスはCPUの思考時間が約450msに制限されていると案内されています。このCPUの手とヒントが同じ処理かどうかは、確認済み事項にはありません。作品を開いて確かめてください。
一歩のヒントを設計するときの判断
一歩を取り出す処理自体は短くても、決めることはいくつか残ります。
最短を保証するかどうか
迷路の距離マップは、最短であることを保証できます。ただし、状態が巨大なゲームでは保証が重くなります。探索を途中で打ち切れば、「最短ではないが前進にはなる手」を返せます。かたちのあいだのヒントが「最短とは限らない」と明記しているように、保証の有無は仕様として書いておくのが誠実です。保証しないなら、そう書きます。
同点のときの選び方
隣のマスが同じ残り歩数なら、どちらも最適です。コードでは先に見つかった方向を返します。この選び方が固定されていると、同じ場所で何度押しても同じ向きが出ます。ばらつかせたいなら乱数を使えますが、テストしにくくなります。
理由を添えるか
潮目のオセロには、理由付きヒントがあります。「右へ」だけより、「ここが近道です」と添えた方が、プレイヤーは次から自分で判断できるようになります。ただし理由の文を作るには、評価の内訳(なぜその手が良いか)を記録しておく必要があります。距離マップだけでは、「近い」以上の理由は言えません。
使える回数と手数の扱い
ヒントを無制限にすると、パズルが作業になります。かたちのあいだの「一手戻す」は手数が減らないと案内されています。ヒントや戻す操作と記録の関係を決めておくと、プレイヤーは納得して使えます。公式作品の記録がどう扱われるかは、各作品の画面で確かめてください。
起こりやすい誤り
- 壁や取れない手の判定を、距離計算とヒント選択で別々に書いてしまいます。open のような判定を一つにまとめないと、ヒントが壁へ向かうバグが出ます。
- 距離マップを、プレイヤーが動くたびに作り直します。ゴールが変わらないなら一度で足ります。ゴールが動くゲームでは、動くたびの再計算が必要です。
- 到達不能の場合を考えていません。ヒントが null や無意味な向きを返す場面を、先に決めておきます。
- ヒントの出力を、そのまま解答として見せてしまいます。連続で押せる設計なら、実質的に解答表示と同じです。回数制限か待ち時間を検討します。
- スライドパズルで、状態の表現が定まっていません。盤面を文字列にして重複を判定すると、訪れた配置を見分けやすくなります。
次の行動
上のコードを自分の迷路に差し替えて動かし、スタート位置を変えながら、ヒントが毎回最短の向きを指すか確かめてみてください。そのうえで、hint の戻り値を使って「残り歩数だけ」「向きだけ」「向きと理由」の3通りの表示を作り、どれが遊びを邪魔しないかを比べます。公式作品の月夜の配達、プリズムの道、かたちのあいだでヒントを使い、自分の設計と見比べるのも有効です。

