落下点を計算するが、少しだけ外す相手

遊べるポン AI に必要なのは二つ。ボールの落下点を正確に求めること、そして毎回わざと少し外すこと。前者は三角波一回、後者はラリーごとに引き直します。

予測しない相手は退屈です。完璧に予測する相手には勝てません。ポンの AI に必要なのはちょうど二つ、落下点を正しく求めることと、毎回少し外すことです。

未来をシミュレートせずに落下点を出す

ボールを一フレームずつ進めて衝突を判定しながら未来を探すと、予測が「先回りのシミュレーション」になります。遅いうえに実際の物理からずれていきます。安い方法は、コートを上下の壁で鏡像展開して無限の帯にすることです。展開した空間ではボールは直線を進むので、落下点はその直線の終点をコートに折り返すだけです。

折り返しは三角波です。

const low = radius;
const high = courtH - radius;
const span = high - low;
const period = span * 2;
const folded = ((raw - low) % period + period) % period;
return low + (folded <= span ? folded : period - folded);

一回の評価、ループなし、ボールの向きにも依存しません。落とし穴が一つ。縦速度がゼロのときは現在の y をそのまま返す必要があります。そうしないと折り返しが 20.000000000000007 のような値を返します。見た目は変わりませんが、テストの等値比較がランダムに落ちます。

誤差は正しい場所で揺らす

AI の目標は 落下点 + 偏差 です。偏差はプレイヤーが打ち返すたびに引き直し、毎フレーム揺らしません。毎フレームだとパドルが震えるだけで、判断ミスには見えません。ラリーごとに引き直すと、「この一打は読み違え、次は正確」というリズムが出ます。

偏差はコート高さの十分の一ほど。小さすぎれば負けませんし、大きすぎれば考えていないように見えます。プレイヤーのパドルは AI より少し速いので、勝ち方は速度勝負ではなく逆サイドへ打ち込むことです。誤差を抱えた相手に余分に走らせます。

ルールと描画を分ける

ルールは src/games/pong/logic/game.ts にあります。import ゼロ、DOM に触れず、step(state, { target, dt }) が新しい状態を返します。乱数の状態も状態の中に持つので、同じ種で二百フレーム進めれば必ず同じ結果になります。この性質はコメントではなくテストの assertion です。

描画側の仕事は三つ。100×60 の抽象座標をキャンバスに写すこと、現在のテーマ色を CSS 変数から読むこと、スコアを HUD に書くこと。パドルとボールの色は --c-accent と --c-text から取るので、サイトをダークモードにするとコートも塗り替わります。

ボールが円ではなく四角なのは手抜きだけではありません。このサイトに丸みは一切なく、円が一つあると別の場所から来たように見えます。

まとめ

AI の価値は強さではなく読みやすさです。相手が計算していること、そして間違えることを、プレイヤーが感じられなければなりません。その二つが成立していれば、難易度を速度で上げる必要はありません。

← 記事一覧に戻る

コメント

…