GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css tic tac toe 9パターン — 盤が自分で数えて線を引く三目並べ

css tic tac toe の盤に駒を置く前に、コンピューターは残る終局を数えている — 03番の点数ヒートと04番の勝敗リングがその計算をマスの上に描く。九つの盤はどれも、勝ち負けの読みを数字ではなく色と線で見せます。

九つの三目並べの盤を画面でそのまま打ってみる

自動再生 · タイルを押すとその項目へ · すべて1つのzip

九つの盤は、駒が一つ置かれる瞬間から始まり、盤全体を読む計算へ広がっていく順に並んでいます。01は駒が筆順どおりに描かれてマスが沈んで戻り、02はその盤がポインターに合わせて傾き、03と04はマスの上に点数と勝敗の割合を重ねます。05は終局の瞬間の線、06は盤の中の盤で、07と08は同じ局面を折りたたんだり木に広げたりして見せ、09は二つのコンピューターが自ら打つ盤をチョークの質感で締めくくります。

css tic tac toe 9パターン — 計算がマスの上に描かれる三目並べ

盤はキャンバス一枚に描きます。駒とマスと点数の色を塗り、次の一手を決める仕事は index.html の JavaScript が受け持ち、CSS が決めるのは舞台の大きさと、その上に重なる状態表示・ボタン、キーボードフォーカスの輪郭線だけです。舞台 .tt の幅は min(92vw, 160vh - 12.8vw) で、480:300 の比率を保ちます。キャンバスの内部解像度は480×300に devicePixelRatio(最大2)を掛けた大きさなので、ウィンドウが縮んでもマスの座標は変わりません。ポインターの位置は getBoundingClientRect の比率で480×300の座標に直して使います。

対局の進み方は九つの盤で共通です

人がX、コンピューターがOを持ちます。人がマスを押すと駒が置かれ、420ms後に bestCell が全探索の点数が最も高いマス(同点なら番号の小さいほう)へOを置きます。その420msの間に来た入力と、すでに埋まったマスへの入力は捨てられます。

人の入力が2.4秒(150ティック)途切れると、両方をコンピューターが打ちます。Xは空きマスが6個を超えるか30%の確率のときに、種を固定した乱数でマスを選び、残りは bestCell です。05は八本の線を決まった順に一局ずつ完成させる台本の対局で、06は小さい盤の規則が別にあるので chooseMove の加点式で打ちます。終局のあとに人が押すと新しい対局が始まります。

入力はマスごとのボタンではなく、キャンバスの上のカーソルです

キャンバスが tabindex="0" と aria-label を持ち、矢印キーでカーソル(3×3、06は9×9)を動かして、EnterかSpaceで置きます。マスごとに <button> を置かないので、<button> は停止・再生の一つだけで、カーソルのマスはキャンバスにフォーカスがあるときだけ白い2.5pxの枠で描かれます。

状態行の aria-live="polite" は「Xの番」「Oの勝ち」のような文言が変わったときだけ更新し、キーボードのカーソルがあるときは末尾に「マス 行-列」の位置が付きます。

時間と乱数は固定されています

ハーネスが16msの固定ステップ(FIXED_DT)で scene.step() を呼び、描画は requestAnimationFrame の一回につき一度です。乱数は種を固定した線形合同法の lcg(seed) だけで作り(場面ごとに種は別で、01は makeMatch に11を渡し、03は lcg(31)、06は lcg(61)、07は lcg(71)、09は lcg(91) で、09は背景用に lcg(5) も別に持ちます。05は台本の対局なので種では変わりません)、Math.random・new Date・Date.now・fetch・localStorage は九つのファイルのどれにも出てきません。

この盤をどんな画面に置くか

01はモバイルの対戦アプリやボードゲームの紹介ページ、02はボードゲームカフェのランディングやおもちゃ店のヒーローに似合います。03は学習用のゲーム解説やAI相手の難易度案内、04は戦略ゲームのチュートリアルや手を読む練習ページに向いています。

05は勝利画面や結果の共有カード、06はボードゲームのポートフォリオや対戦モードの紹介に合います。07は数学の教育コンテンツやパズルの解説、08はアルゴリズムの講義資料や開発ブログの図解、09は教育サービスのランディングや開発者イベントのポスターに入れやすい盤です。

html css tic tac toe を技法別のコードで見る

各断片は index.html の <script> から連続した行をそのまま切り出したもので、25行を超えません。ファイルの前半には駒の描画・ハーネス・進行役の共通コードが約520行あり、場面ごとのコードは528行目あたりから始まります。tic tac toe html css code を探す人が思い浮かべるマスのクリック処理より一段内側、盤が値を計算して描く場所を選んで載せました。

01マスを押すとXとOが筆順どおりに描かれ、マスがバネで沈んで戻る置き方

Xは二画を順に、Oは12時の位置から時計回りに一周、setLineDash の長さを lineDashOffset で縮めながら引き、マスの大きさは a = -SPRING_K * (pos[i] - 1) - damp * vel[i] という減衰バネの式で、一度行き過ぎてから落ち着きます。下の断片は、駒の置かれたマスごとに16msの一歩を四つに割ってその式を積分する場所です。このマスの累積面積は19.3%、平均強度は45.1、動いたフレームは21/23でした。

マスを押すとXとOが筆順どおりに描かれ、マスがバネで沈んで戻る置き方
新しいタブで開く

01 · マスを押すとXとOが筆順どおりに描かれ、マスがバネで沈んで戻る置き方 — 計測 累積面積 19.281% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

MARK_MSOVERSHOOTSTROKE_WSPRING_K
                const dt = FIXED_DT / 1000;
                for (let i = 0; i < 9; i += 1) {
                    if (age[i] < 0) {
                        continue;
                    }
                    age[i] += FIXED_DT;
                    // 16msの一歩を四つに割って積分する — 一度に進むと、式が狙った行き過ぎより小さく出る
                    for (let q = 0; q < 4; q += 1) {
                        const a = -SPRING_K * (pos[i] - 1) - damp * vel[i];
                        vel[i] += (a * dt) / 4;
                        pos[i] += (vel[i] * dt) / 4;
                    }
                }

02ポインターに合わせて遠近で傾き、駒が落ちて収まる間は高さに応じて影が長くぼやける立体盤

盤は252pxの正方形で、ポインターの位置は lerp(ax, tx, LERP_K) でティックごとに傾きへ移され、目標の95%に届くまで32ティック、512msかかります。影は駒の高さに比例して長く太くなりますが、ぼかしは本物のブラーではなく太さの違う四本を重ねた線で、座った駒は長さ9.9px・外側の太さ約17.5px、高さ90pxから落ちる瞬間は長さ59.4px・太さ約56pxです。レンダーで測った値は累積面積26.7%、平均強度45.6、動いたフレーム23/23でした。

ポインターに合わせて遠近で傾き、駒が落ちて収まる間は高さに応じて影が長くぼやける立体盤
新しいタブで開く

02 · ポインターに合わせて遠近で傾き、駒が落ちて収まる間は高さに応じて影が長くぼやける立体盤 — 計測 累積面積 26.728% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TILT_MAXDEPTH_PXSHADOW_LENLERP_K
                for (let i = 0; i < 9; i += 1) {
                    if (who[i] === 0) {
                        continue;
                    }
                    const hgt = zb[i] + DEPTH_PX;
                    const off = hgt * SHADOW_LEN;
                    const blur = 1 + hgt * 0.16;
                    const alpha = clamp(0.5 - hgt * 0.0016, 0.18, 0.5);
                    for (let pass = 3; pass >= 0; pass -= 1) {
                        strokeMark(ctx, who[i], i, 0.5, 'rgba(10,8,22,' + (alpha / 4).toFixed(3) + ')', 7 + blur * pass * 0.9, off * 0.5, off * 0.8);
                    }
                }

03空きマスごとに最後まで読み切った点数が熱の色で敷かれ、最善のマスが灯る盤

空きマスごとに、そこへ置いた後の盤を solve が終局まで展開し、Xが勝てば SCORE_WIN - ply、負ければその負の値、引き分けなら0を上へ返します。点数はミント(正)とピンク(負)の濃さで塗り、空の盤ではどの初手も0なので数字は出さず、そのマスの後に打つ側が勝つ手と負ける手の差を終局の数で割った比率だけで淡い色が分かれ、最善のマスは700ms後から枠が脈打ちます。この盤は累積面積37.2%、平均強度30.2、動いたフレーム21/23でした。

空きマスごとに最後まで読み切った点数が熱の色で敷かれ、最善のマスが灯る盤
新しいタブで開く

03 · 空きマスごとに最後まで読み切った点数が熱の色で敷かれ、最善のマスが灯る盤 — 計測 累積面積 37.219% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SEARCH_DEPTHSCORE_WINHEAT_ALPHATHINK_MS
        let best = turn === 1 ? -1000 : 1000;
        let any = false;
        for (let i = 0; i < 9; i += 1) {
            if (b[i] !== 0) {
                continue;
            }
            b[i] = turn;
            const v = solve(b, 3 - turn, ply + 1, depth - 1, cnt);
            b[i] = 0;
            any = true;
            if (turn === 1 ? v > best : v < best) {
                best = v;
            }
        }
        return any ? best : 0;

04マスに乗せると、そこへ置いた後に起こりうる終局の勝・分・負の割合が半透明の駒の周りにリングで広がる盤

指したマスに駒を置いたことにして、enumerate が終局を深さ優先で数え、X勝・引き分け・O勝の割合をリングの弧の長さに変えます。空の盤で角のマスを指すと訪れる局面が59,705個で ENUM_CAP の20,000を超え、終局27,732のうち9,074個だけを数え、そのときリングの外に点線のリングが出て状態行に「上限まで集計」と付きます。レンダーの値は累積面積22.5%、平均強度115.8、動いたフレーム21/23でした。

マスに乗せると、そこへ置いた後に起こりうる終局の勝・分・負の割合が半透明の駒の周りにリングで広がる盤
新しいタブで開く

04 · マスに乗せると、そこへ置いた後に起こりうる終局の勝・分・負の割合が半透明の駒の周りにリングで広がる盤 — 計測 累積面積 22.503% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ODDS_RADIUSHOVER_LERPGHOST_ALPHAENUM_CAP
    function enumerate(b, turn, t, cap) {
        if (t.capped) {
            return;
        }
        t.nodes += 1;
        if (t.nodes > cap) {
            t.capped = true;
            return;
        }
        const w = lineOf(b);
        if (w >= 0) {
            if (b[LINES[w][0]] === 1) {
                t.x += 1;
            }
            else {
                t.o += 1;
            }
            return;
        }

05三つが揃うと線がマスの中心を結ぶように引かれ、光がにじんで静まる勝利演出

八通りの線の組み合わせを手番ごとに調べ、揃った線が見つかると、両端のマスの中心からマスの大きさの0.3倍だけ外へ伸ばした線を LINE_MS の420msで引きます。にじみは shadowBlur に GLOW_BLUR × 進行を与え、引き終わったあとは Math.cos(fade * Math.PI / 2) で900msかけて0まで減らしますが、この値は変換で拡大されないため devicePixelRatio を掛けて補正しています。測定は累積面積11.4%、平均強度97.6、動いたフレーム14/23でした。

三つが揃うと線がマスの中心を結ぶように引かれ、光がにじんで静まる勝利演出
新しいタブで開く

05 · 三つが揃うと線がマスの中心を結ぶように引かれ、光がにじんで静まる勝利演出 — 計測 累積面積 11.423% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LINE_MSGLOW_BLURWIN_LINESFADE_ALPHA
                    x0 -= ux * CELL * 0.3;
                    y0 -= uy * CELL * 0.3;
                    x1 += ux * CELL * 0.3;
                    y1 += uy * CELL * 0.3;
                    const prog = smooth(winAge / LINE_MS);
                    // にじみの半径 — 線が伸びる間は大きくなり、引き終わったあとはサイン曲線に沿って小さくなる
                    const fade = clamp((winAge - LINE_MS) / GLOW_FADE_MS, 0, 1);
                    const glow = GLOW_BLUR * (winAge < LINE_MS ? prog : Math.cos(fade * Math.PI / 2)) * dprG;
                    ctx.save();
                    ctx.shadowColor = rgba(MINT, 1);
                    ctx.shadowBlur = glow;
                    ctx.strokeStyle = rgba(mix(MINT, CREAM, 0.35), 1);
                    ctx.lineWidth = 9;
                    ctx.lineCap = 'round';
                    ctx.beginPath();
                    ctx.moveTo(x0, y0);
                    ctx.lineTo(lerp(x0, x1, prog), lerp(y0, y1, prog));
                    ctx.stroke();
                    ctx.restore();

06盤の中に盤があり、置いたマスが次の盤を指定して、画面がその盤へバネで拡大される入れ子の盤

置いたマスの番号が次に打つ小さい盤の番号になり、その盤がすでに決着しているか埋まっていれば、どの小さい盤でも選べます。goal() が決めた小盤の中心と ZOOM_K の2.6倍を、カメラの位置と倍率がそれぞれバネで追い、倍率は頂点2.725まで行き過ぎてから576msで移動距離の2%以内に落ち着きます。この入れ子の盤は累積面積59.0%、平均強度32.3、動いたフレーム19/23で、九つの中で面積が最も広い盤です。

盤の中に盤があり、置いたマスが次の盤を指定して、画面がその盤へバネで拡大される入れ子の盤
新しいタブで開く

06 · 盤の中に盤があり、置いたマスが次の盤を指定して、画面がその盤へバネで拡大される入れ子の盤 — 計測 累積面積 59.026% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ZOOM_KFOCUS_MSDIM_ALPHASPRING_ZETA
        // カメラの目標 — 指定された盤があればその盤へズーム、なければ全体
        function goal() {
            if (fb >= 0) {
                return [(fb % 3) * SMALL + SMALL / 2, Math.floor(fb / 3) * SMALL + SMALL / 2, ZOOM_K];
            }
            return [BIG / 2, BIG / 2, 1];
        }

07回しても裏返しても同じ局面を一つの盤に折り重ねる対称の折りたたみ表示

回転四通りと反転二通りを掛けた八つの変換を buildSym がマスの入れ替え表にし、局面を八回入れ替えて文字列につなぎ、いちばん小さいものを標準形にします。半透明の八枚の盤が中央から半径104pxの輪に広がり、800msかけて一か所に折りたたまれ、状態行には異なる形 n/8 が出ます。レンダーの値は累積面積29.1%、平均強度70.0、動いたフレーム21/23でした。

回しても裏返しても同じ局面を一つの盤に折り重ねる対称の折りたたみ表示
新しいタブで開く

07 · 回しても裏返しても同じ局面を一つの盤に折り重ねる対称の折りたたみ表示 — 計測 累積面積 29.139% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SYM_COUNTFOLD_MSSPREAD_RMERGE_ALPHA
    function buildSym() {
        const out = [];
        for (let f = 0; f < 2; f += 1) {
            for (let r = 0; r < 4; r += 1) {
                const perm = [];
                for (let i = 0; i < 9; i += 1) {
                    let row = Math.floor(i / 3);
                    let col = i % 3;
                    for (let q = 0; q < r; q += 1) {
                        const nr = col;
                        col = 2 - row;
                        row = nr;
                    }
                    if (f === 1) {
                        col = 2 - col;
                    }
                    perm.push(row * 3 + col);
                }
                out.push(perm);
            }
        }
        return out;
    }

08現在の局面から分かれる手を枝に広げ、その先の終局の数で枝の太さが変わる木の表示

根は現在の局面を示す54pxの小さな盤で、空きマスごとに半径70pxの円の上へ26pxの子の盤が55msずつずれて広がり、160ms遅れて孫の点が出てきます。枝の太さは1に、BRANCH_W の5と終局の数の対数比を掛けた値を足したもので、最後まで打った点数が正なら空色、負ならピンク、0ならクリーム色です。この木は累積面積18.6%、平均強度75.0、動いたフレーム16/23と測れました。

現在の局面から分かれる手を枝に広げ、その先の終局の数で枝の太さが変わる木の表示
新しいタブで開く

08 · 現在の局面から分かれる手を枝に広げ、その先の終局の数で枝の太さが変わる木の表示 — 計測 累積面積 18.648% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

RING_RNODE_RBRANCH_WEXPAND_MS
        function branch(ctx, x0, y0, x1, y1, leaves, mx, prog, col, hot) {
            const w = 1 + BRANCH_W * (Math.log(1 + leaves) / Math.log(1 + mx));
            ctx.strokeStyle = rgba(col, hot ? 0.95 : 0.55);
            ctx.lineWidth = w;
            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.moveTo(x0, y0);
            ctx.lineTo(lerp(x0, x1, prog), lerp(y0, y1, prog));
            ctx.stroke();
        }

09チョーク質感の黒板で二つのAIが自ら打ち、粉が舞い、黒板消しで消されていくヒーロー

チョークの線は直線を5pxごとに区切り、垂直方向へ値ノイズ × 1.8pxを足して揺らし、粉は90個のプールから、駒を引くティックごとに2個ずつ生まれて重力と風を受けます。最後の一手の400ms後に黒板消しが800msかけて x 74 から406まで掃き、盤を空にして、このとき帯の右側だけ駒を描くので、通った跡が消えて見えます。この黒板は累積面積45.6%、平均強度85.6、動いたフレーム22/23でした。

チョーク質感の黒板で二つのAIが自ら打ち、粉が舞い、黒板消しで消されていくヒーロー
新しいタブで開く

09 · チョーク質感の黒板で二つのAIが自ら打ち、粉が舞い、黒板消しで消されていくヒーロー — 計測 累積面積 45.573% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CHALK_NOISEDUST_NTURN_MSERASE_MS
        function chalkLine(x0, y0, x1, y1, seed) {
            const len = Math.hypot(x1 - x0, y1 - y0);
            const n = Math.max(2, Math.ceil(len / 5));
            const nx = -(y1 - y0) / len;
            const ny = (x1 - x0) / len;
            const out = [];
            for (let k = 0; k <= n; k += 1) {
                const t = k / n;
                const off = (noise2(t * len * 0.045, seed * 7.31, 3) - 0.5) * 2 * CHALK_NOISE;
                out.push([lerp(x0, x1, t) + nx * off, lerp(y0, y1, t) + ny * off]);
            }
            return out;
        }

値が盤の印象を変えるところ — 弾みの強さ・探索の深さ・にじみの半径

三つの値は画面を撮って測ったのではなく、ソースにある関数をノードでそのまま呼んで数えたものです。レンダーで測った数値は、後ろの表にまとめました。

弾みの強さを1.1から1.6に上げると、落ち着くまでが368msから1,424msに延びる

01の OVERSHOOT が1.1のとき、減衰比は0.591で、マスは240msで頂点の1.093倍に達し、368ms以内に2%以内へ収まります。1.6に上げると減衰比は0.160に下がり、頂点は192msで1.598倍になって、落ち着くまで1,424msかかります。Oの応答は420ms後に来るので、その間に次の駒が置かれても、前の駒はまだ長く揺れていることになります。

探索の深さを9から3に下げると、ヒートが平らになる

03の SEARCH_DEPTH が9なら空の盤で549,945個の局面を調べます(ノードで cellScores を一回呼ぶのに約13ms)が、3なら585個しか見ません。駒が1〜2個置かれた全局面は81個で、その候補は576マスあり、符号つきの点数が出るマスは深さ9で276マス、深さ3では0マスです。

にじみの半径を6から20に上げると、光の届く面積が約2.3倍になる

05の GLOW_BLUR が6のとき、280pxの線一本(太さ9px)を480×300のキャンバスに描くと、中心線から光が届く距離は10pxで、届くピクセルは5,900個です。20に上げると22pxと13,348個になり、面積は約2.3倍なので、マスの間のすき間まで光がにじみます。

レンダーした九つの場面の面積・強度・フレームは、次の表に集めました。動いたフレームは項目ごとに14〜23と幅があり、05が14/23で最も低い理由は、この測定だけでは切り分けられませんでした。

項目 累積面積 平均強度 動いたフレーム
01 バネの駒置き 19.3% 45.1 21/23
02 立体の盤 26.7% 45.6 23/23
03 点数ヒート 37.2% 30.2 21/23
04 勝敗リング 22.5% 115.8 21/23
05 勝利の線 11.4% 97.6 14/23
06 入れ子の盤 59.0% 32.3 19/23
07 対称の折りたたみ 29.1% 70.0 21/23
08 ゲームの木 18.6% 75.0 16/23
09 黒板の自動対局 45.6% 85.6 22/23

どこで壊れるか — 速く連打するとき・終局のあと・動きを減らす設定

連打すると、Oの応答の420msの間は入力が消える

九つの盤とも、人が打ったあとの420msは turn !== 1 なので新しい入力を捨てます。連打が効かないように見える区間で、すでに駒のあるマスを押した入力も同じ理由で無視されます。

02は落ちてくる駒と押すマスがずれて見えることがある

クリック判定の hit は駒の高さではなく、台座の面(z = 0)の四角形で計算します。最大の傾き0.42radのとき、高さ90pxの駒は遠近を無視すると画面上で約37px上に浮いて見え、proj() で遠近を入れると真ん中の行で約43px(行によって31〜52px)になるので、落ちている間は絵の駒と押すべきマスがずれます。

04の空の盤の割合は、前のマスの側へ偏る

上限20,000で止まる集計は深さ優先なので、番号の小さいマスの側の枝から先に数えます。中央にXが一つあり、最初の空きマスを指した場合は、訪れる局面が6,812個で上限の下に収まり、終局を最後まで数えます。

05は終局のあとに押すと、演出が途中で切れる

勝利の線を引いている最中でも、にじみが静まっている最中でも、人が押せば新しい対局になるので、効果はその場で消えます。引き分けのときは線もにじみもありません。

06は全体表示でマスが小さい

マスはキャンバス座標で、全体表示では30px、ズームでは78pxです。320×200のビューポートでは .tt の幅が約279pxに縮み、それぞれ約17pxと約45pxになるので、全体表示のタッチ対象としては小さすぎます。小さい盤が決まっていない自由選択の瞬間も、カメラは全体へズームアウトしますが、クリックはマスの座標で計算されます。

07は折りたたまれている間、タップを受け付けない

折りたたみと落ち着きの段階の1,060msは入力を無視します。中央の盤の形が標準形に変わり、実際のマスとずれるからです。到達できる局面5,478個は標準形で765個に折りたたまれ、角のXと辺のOの局面は変形八つがすべて異なるので、異なる形は8/8まで行きます。

08は空の盤で太さの差がほとんどない

空の盤の下の終局は255,168個で、子ごとには25,872〜29,592個なので、太さは5.9〜6.0pxの範囲に集まります。駒が2個の盤の子7個も終局が441〜576個で太さの差は0.2px程度、差が大きく出るのは孫の層です。

09は背景が高解像度でぼやける

背景のにじみを一度焼き付けるオフスクリーンキャンバス(480×300)には devicePixelRatio を掛けていないので、dpr 2の画面では二倍に引き伸ばされてぼやけます。粉のプールが90個で埋まると、いちばん古いものから消えます。

上の三つの値を自分で書き換えて試したい場合は、下のカードから圧縮ファイルを受け取ってください。展開するときに入力するパスワードは gvrueby4 で、盤ごとにHTMLとSCSSの一組とReact版が入っています。

変形三つとアクセシビリティ

名前 値 印象
弾みの強さを変える 01 OVERSHOOT 1.1 → 1.6 行き過ぎが大きくなる。減衰比が0.59から0.16に下がり、ピークは1.09倍から1.60倍へ上がり、2%以内に収まる時間は約370msから約1,420msに延びて、駒がしばらく揺れ続ける。
探索の深さを変える 03 SEARCH_DEPTH 9 → 3 ヒートが平らになる。駒が1〜2個置かれた全81局面の候補576マスのうち、符号つきの点数は深さ9で276マス、深さ3で0マスになり、空の盤の探索局面は549,945から585に減って、考える時間がほぼ消える。
にじみの半径を変える 05 GLOW_BLUR 6 → 20 光が広くにじむ。280pxの線一本を480×300に描いたとき、中心線から光が届く距離が10pxから22px、光が届く面積が5,900pxから13,348pxへ約2.3倍になり、マスの間までにじむ。

三つの変形の数字も、上の値の節と同じやり方でソースの関数をノードから直接呼んで数えたものです。

入力の処理は、九つのファイルが同じ骨組みを使っています。どのファイルも <button> は一つだけで、キャンバスは tabindex="0" と aria-label を、状態行は aria-live をそれぞれ一度ずつ持ちます。preventDefault は矢印キーの分岐とEnter・Spaceの分岐の二か所でだけ呼ぶのでTabは塞がれず、キャンバスからボタンへフォーカスが移れます。setPointerCapture は九つのファイルのどれも使いません。

prefers-reduced-motion は、JavaScriptの matchMedia 一か所とCSSの一か所で読み取ります。オンのときは requestAnimationFrame のループを始めず、三手を置いた場面を一枚だけ描き、入力が来たら show() で補間なしにすぐ描き直して、Oの応答も待ちません。ボタンは「次の場面」に変わります。01はバネなしで最終の大きさと描き終えた筆跡を、02は傾き0と落下の高さ0を、04はリングが目標の割合にすぐ届いた姿を、06はカメラが目標へ即座に合った姿を見せます。07は折りたたみなしで八つの変形を輪に固定し、08は枝を即座に広げ、09は粉と黒板消しがありません。

tic tac toe html css javascript だけで足りるか?

九つのファイルにライブラリ・CDN・WebGLはなく、<script src も http も0個なので、ファイル一つあれば動きます。点数ヒートの探索、勝敗リングの集計、対称の折りたたみ、ゲームの木が、すべてこの一つのファイルの中のJavaScriptで計算されます。

足りないのは、サービスとして作るときの部分です。勝敗の記録を保存する localStorage も、効果音を鳴らす AudioContext もなく、二人が向かい合って打つモードもありません。ボールを弾いてブロックを崩す盤の反射角はjavascript ブロック崩し 反射角で変わる9パターンで、経路を探すヘビはcss snake game 9パターン — キー操作なしで経路を探して餌を食べるヘビで続きを読めます。ジャンルをもっと広く見たいときは、CSS ゲーム 作成 — CSSだけで動くミニゲーム9選とHTML CSS JS ゲーム 9種 — 電卓・タイマー・パズルのコピペをあわせて開いてください。

tic tac toe game html css js のコードを使い回す前によくある質問

html css tic tac toe のソースをそのまま自分のサイトに貼ってもいいですか?

外部のアドレスを呼ばないので、貼るときについて回る依存の一覧はありません。ファイルごとに index.html 一つにスタイルとスクリプトが入っていて、乱数は種で固定されているため同じ画面が再現されます。ただし画面の文言は、受け取る圧縮ファイルの言語に合わせて入っています(日本語版の圧縮ファイルなら日本語です)が、文字列として直接書かれているので、サイトの言語が違うときは使う場所の言葉に書き換えてください。

コンピューターの手はどこで決めていますか?

人が打ったあとのOの応答は、01〜05と07〜09では bestCell が全探索の点数がいちばん高いマスを選び、同点なら番号の小さいマスです。06だけは小さい盤があるので、chooseMove が線の完成 +100・相手の線を止める +40・中央のマス +6 といった加点から、相手がすぐ勝てる盤を渡す手の −30 を引き、lcg のノイズを足して決めます。03の点数は全探索の値で、04の勝敗の割合は上限つきの終局の集計ですから、どちらも盤の読み方を見せるだけで、勝ちを約束する値ではありません。

毎回違う対局にするには、どこを変えますか?

自動対局でのXの初手は lcg の種で決まるので、種の数字を変えれば別の対局になります(05は台本の対局なので、種を変えても変わりません)。毎回ランダムにしたいなら、その場所で使う値を自分で作って入れる必要があり、この記事のファイルはそうした値を作りません。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。