マガジン

文字だけで動くゲームを作る:等幅フォントが画面になる

遊びをつくる

執筆にClaudeを使用しています。

色鉛筆の線やゲームのスケッチが描かれたノートとカード。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

画像もCanvasも使わず、@ という1文字が # の壁をよけて歩くだけのゲームを考えてみます。文字を並べるだけで盤面になるのは、等幅フォントでは1文字が同じ幅の1マスとして並ぶからです。配列の「3列目・2行目」と、画面上の「3文字目・2行目」が同じ場所を指します。座標の計算と描画が一致するため、ゲームの骨組みが驚くほど少なくなります。 ただし、この性質は条件つきで成り立ちます。日本語の全角文字を混ぜた場合や、環境によって等幅フォントの種類が変わった場合に、列がそろい続けるかは確かめる必要があります。この記事は、この問いを自分の環境で確かめる実験の設計書です。最小のゲームを作り、条件を一つずつ変えて、どこまで「文字が画面になる」かを記録します。結果は書きません。読者の手元の環境で出た答えが、そのまま判断材料になります。

この記事の目次
  1. 実験の問いと準備するもの
  2. 手順:基準を作ってから一つずつ崩す
  3. 記録の取り方
  4. 結果の読み方
  5. 起こりやすい誤り
  6. 次の行動

実験の問いと準備するもの

確かめる問いは次の二つです。

  1. 半角文字だけで盤面を組んだとき、1回の操作で @ は画面上でちょうど1マス動いて見えるか。
  2. 全角文字・フォント指定・画面幅のどれを変えると、列のそろいが崩れるか。

準備するものは、テキストエディタ、ブラウザ(できれば異なる2種類以上)、可能なら別の端末(スマートフォンなど)です。ライブラリやサーバーは使いません。1つのHTMLファイルを開くだけで動きます。

下のコードを index.html という名前で保存します。矢印キーで @ を動かし、G に着いたらメッセージが出る迷路です。壁は #、床は半角スペースです。

html
<!doctype html>
<meta charset="utf-8">
<style>
  #screen { font: 28px/1 monospace; }
</style>
<pre id="screen"></pre>
<p id="msg">矢印キーで @ を G まで動かします。</p>
<script>
const rows = [
  "##########",
  "#@  #    #",
  "# # # ## #",
  "# #   #  #",
  "# #####  #",
  "#      #G#",
  "##########"
];
const grid = rows.map(r => r.split(""));
let x = 1, y = 1;
grid[1][1] = " ";
const s = document.getElementById("screen");
function draw() {
  const view = grid.map(r => r.slice());
  view[y][x] = "@";
  s.textContent = view.map(r => r.join("")).join("\n");
}
addEventListener("keydown", e => {
  const d = {ArrowUp:[0,-1], ArrowDown:[0,1],
             ArrowLeft:[-1,0], ArrowRight:[1,0]}[e.key];
  if (!d) return;
  e.preventDefault();
  const nx = x + d[0], ny = y + d[1];
  if (grid[ny][nx] === "#") return;
  x = nx; y = ny;
  draw();
  if (grid[y][x] === "G")
    document.getElementById("msg").textContent = "到着しました。";
});
draw();
</script>

仕組みは単純です。盤面は文字の二次元配列で、@ の位置だけを x と y で別に持ちます。描画のたびに配列を複製して @ を上書きし、行を改行でつないで <pre> に流し込みます。キーが押されたときだけ描き直すターン制なので、毎フレームの更新処理は要りません。

手順:基準を作ってから一つずつ崩す

条件を変えるときは、必ず一度に一つだけにします。複数を同時に変えると、どれが原因か分からなくなります。

手順1:基準条件を確認する

保存したファイルをブラウザで開き、迷路を G まで解きます。このとき次の三点を目で確かめます。

  • 壁の縦の列が、上から下までまっすぐ並んでいるか。
  • 矢印キーを1回押すと、@ は1マスだけ動くか。
  • 壁に向かって押したとき、@ が動かず画面も乱れないか。

ここで崩れが見つかる場合は、環境側の問題です。以降の条件に進む前に、原因を先に切り分けます。

手順2:1文字の寸法を測る

縦移動と横移動で、見た目の移動距離は同じでしょうか。文字は一般に縦長なので、同じ1マスでも縦と横で距離が違って見える可能性があります。ブラウザの開発者ツールのコンソールに、次の3行を貼って実行します。

js
const r = document.createRange();
r.setStart(s.firstChild, 0); r.setEnd(s.firstChild, 1);
const b = r.getBoundingClientRect(); console.log(b.width, b.height);

出てきた幅と高さを記録し、高さを幅で割った値を「縦横比」とします。1に近いほど正方形のマスに近く、1より大きいほど縦長のマスです。

手順3:条件を一つずつ変える

基準のあと、次の4条件を別々に試します。

  • 条件A(全角の壁):壁の # を全角の ■ に置き換えます。ただし、床のスペースは半角のままにします。
  • 条件B(全角のキャラ):draw の中の "@" を "勇" に変えます。盤面の他の部分はそのままにします。
  • 条件C(フォント指定):CSSの monospace を、"Courier New", monospace や "MS Gothic", monospace など別の指定に変えます。指定したフォントがない環境では代替フォントになるため、その点も観察対象です。
  • 条件D(画面幅):ブラウザの幅を狭める、拡大率を上げる、スマートフォンで開くなどして、行が折り返されたり横にはみ出したりしないかを見ます。

条件Aと条件Bは、全角文字の幅が半角のおおむね2倍になるという一般的な性質を確かめるものです。実際に何倍に見えるかは、フォントによって異なる場合があるため、手元で測ります。

記録の取り方

結果は表にまとめると読み返しやすくなります。1条件につき、最低でも2種類のブラウザまたは端末で、それぞれ最初からGまで1回通して遊びます。

  • 基準、環境:、列はそろうか:、1回の操作で1マスか:、縦横比:、気づいたこと:
  • A 全角の壁、環境:、列はそろうか:、1回の操作で1マスか:、縦横比:、気づいたこと:
  • B 全角のキャラ、環境:、列はそろうか:、1回の操作で1マスか:、縦横比:、気づいたこと:
  • C フォント変更、環境:、列はそろうか:、1回の操作で1マスか:、縦横比:、気づいたこと:
  • D 画面幅、環境:、列はそろうか:、1回の操作で1マスか:、縦横比:、気づいたこと:

「列はそろうか」は、壁の縦線が崩れていれば「崩れ」、気にならなければ「そろう」と二択で書き、迷ったら画面を保存して後で見比べます。画面全体のスクリーンショットを条件ごとに1枚撮っておくと、判断がぶれません。「1回の操作で1マスか」は、@ の左右にある壁との間隔を見て、ずれがあれば何文字分に見えたかを書きます。

結果の読み方

記録がそろったら、次のように読みます。

  • 基準で崩れず、条件Aや条件Bで崩れた場合:全角と半角の幅の違いが原因です。盤面に使う記号は半角に統一するのが堅実です。どうしても日本語を使いたいときは、全角だけで盤面全体を組み、床も全角スペースにします。
  • 条件Cで環境ごとに見え方が違った場合:フォントの代替が起きています。見た目をそろえたいなら、フォントを指定する代わりに、本文を等幅として扱える範囲の記号に限定する方法があります。
  • 縦横比が1から大きく離れていた場合:横に1マス動くより、縦に1マス動くほうが大きく感じられます。line-height を調整するか、横方向に半角スペースを1つはさむ(2文字で1マスとする)設計を検討します。このときは配列の1マスを2文字として扱うため、描画側の変換も必要です。
  • 条件Dで折り返しや横はみ出しが起きた場合:盤面が画面幅より広いことが原因です。盤面を小さくするか、文字サイズを画面幅に合わせて調整します。折り返しが起きると列が一気に崩れるため、<pre> では折り返さない指定にして、横スクロールで受け止める方法もあります。

判断の軸は、「遊び手が盤面を読み違えるか」です。多少のずれが見えても、操作と結果が一致していれば遊べます。反対に、見た目は整っていても、1回の操作で2マス動いて見えるなら、設計を見直す理由になります。

起こりやすい誤り

実験の途中で、次のような誤りが起きやすくなります。

  • 全角スペースの混入:床を空白にしたつもりが、全角スペースが入っていることがあります。見た目では区別しにくいので、エディタで空白を可視化する設定を使います。
  • 行ごとの文字数のばらつき:1行だけ文字数が違うと、配列の外を参照して動作が止まります。各行の長さが同じかどうかを、先に確認します。
  • 矢印キーでページがスクロールする:e.preventDefault() を忘れると、盤面の操作と同時にページが動きます。コードでは入れてありますが、改造時に消さないようにします。
  • @ を配列に直接書き込む:@ を盤面の配列に書き込むと、通った場所が @ のまま残ります。位置を別の変数で持ち、描画時だけ上書きする形を保ちます。
  • 条件を同時に変える:フォントと全角を同時に変えると、原因が切り分けられません。

次の行動

まず基準のHTMLを保存して開き、G まで遊んで縦横比を測ってみてください。次に、壁を全角にした条件Aだけを試して、列がそろい続けるかを記録します。形になったら、index.html として単独で動く作品になっているので、アソバースの投稿画面からHTMLを直接アップロードして公開する流れに進めます。公開にはメールアドレスの確認が必要です。