乱数を使わないゲームは、毎回同じでも飽きずに遊べるか:決定的ルールで変化をつくる試作と検証
遊びをつくる執筆にClaudeを使用しています。

同じ操作をすれば、必ず同じ展開になるゲームを遊ぶ場面を考えてみます。三回目あたりで、次に何が来るかが分かってきます。五回目には先回りして動けるようになり、十回目には作業になっているかもしれません。乱数を使わないゲームには、こうした「覚えたら終わり」の心配がつきまといます。 一方で、乱数がなくても飽きられにくいゲームはあります。同じ配置に何度も挑戦できるパズルは、その代表例です。毎回同じであることと飽きることは、直結していない可能性があります。飽きを分けるのは、覚えるまでの過程にどれだけ発見が残るかではないでしょうか。 この記事では、その問いを自分の手で確かめる実験を設計します。小さな回避ゲームを一つ用意し、安全な通路の決め方を「周期が短い式」「周期を重ねた式」「プレイヤーの位置に反応する式」の3種類に切り替えて遊び比べます。結果はこの記事には書きません。記録の取り方と読み方を決めておくことで、遊んだ人それぞれが答えを出せる形にします。
この記事の目次
「毎回同じ」と「飽きる」を分けて考える
実験の前に、問いを細かく分けておきます。「飽きない」という感想は曖昧で、そのままでは記録できません。
乱数を使わないゲームで起こりうる飽きは、大きく二つに分けられます。
- 解き終わりによる飽きです。パターンを覚えてしまい、考える余地がなくなった状態です。
- 単調さによる飽きです。まだ覚えていないのに、展開に特徴がなく、区別がつかないまま退屈になる状態です。
この二つは対策が逆です。解き終わりを防ぐには、覚えるのに時間のかかる構造が必要です。単調さを防ぐには、短い区間でも違いが見分けられる構造が必要です。周期を長くすれば前者は和らぎますが、周期が長くても局所的に単調なら後者は残ります。
そこで、この実験で確かめる問いを次の三つに絞ります。
- パターンを覚えるまでに、何回のプレイが必要か。
- 覚えた後も、「もう一度」を押したくなるか。
- 覚える前の段階で、展開の違いを感じられるか。
乱数を使わないゲームでは、変化の源は「ルール」「時間」「プレイヤーの入力」の三つに限られます。以降の試作は、この三つのうちどれが飽きに効くかを切り分ける設計です。
試作の設計:3つの生成ルールを切り替える
題材は、5本のレーンを持つ回避ゲームです。画面には6行が並び、各行に「安全なレーン」が1本だけあります。プレイヤーは一番下の行にいて、左右キーで1マスずつ動きます。行は一定間隔で1段ずつ下に降りてきます。プレイヤーのいるレーンが、下の行の安全レーンと違っていたらゲームオーバーです。
新しい行の安全レーンは、直前の行の安全レーンに −1・0・+1 のどれかを足して決めます。差を1マス以内に抑えているので、プレイヤーが1回の移動で1マス動ければ、常に生き延びる道が残ります。つまり理不尽な詰みは起こりません。変えるのは、この差の決め方だけです。
- 1、差の決め方:8行ごとに「右へ4回、左へ4回」を繰り返す、変化の源:時間(短い周期)
- 2、差の決め方:3行周期と4行周期の値を足し合わせる、変化の源:時間(周期の重ね合わせ)
- 3、差の決め方:プレイヤーが中央より左なら右へ、右なら左へ、変化の源:プレイヤーの入力
モード3だけは、プレイヤーの動きによって展開が変わります。同じ操作を再現できれば同じ展開になるので、ルールとしては決定的です。ただし、人間が毎回まったく同じ操作をすることはほぼないため、体感としては「毎回少しずつ違う」状態になるはずです。この点が飽きに効くかどうかが、実験の焦点の一つです。
以下が単独で動く最小構成です。ファイルを index.html として保存し、ブラウザで開くだけで動きます。外部の通信やライブラリは使いません。
<canvas id="c" width="250" height="300"></canvas>
<pre id="log"></pre>
<script>
const W=5,H=6,g=c.getContext('2d');
const D={
1:n=>n%8<4?1:-1,
2:n=>(n%3+Math.floor(n/4))%3-1,
3:(n,p)=>p<=2?1:-1
};
let mode=1,rows,px,n,last,alive,timer,log=[];
const clamp=v=>Math.max(0,Math.min(W-1,v));
function start(m){
if(m)mode=m;
rows=Array(H).fill(2);px=2;n=0;last=2;alive=true;
clearInterval(timer);timer=setInterval(tick,400);draw();
}
function tick(){
if(rows[H-1]!==px){
alive=false;clearInterval(timer);
log.push(mode+","+n);
document.getElementById('log').textContent="モード,生存tick\n"+log.join("\n");
draw();return;
}
rows.pop();
last=clamp(last+D[mode](n,px));
rows.unshift(last);n++;draw();
}
function draw(){
g.clearRect(0,0,250,300);
rows.forEach((s,r)=>{for(let k=0;k<W;k++){
g.fillStyle=k===s?'#e8f4ea':'#345';
g.fillRect(k*50+2,r*50+2,46,46);}});
g.fillStyle=alive?'#fc4':'#f55';
g.fillRect(px*50+12,(H-1)*50+12,26,26);
}
addEventListener('keydown',e=>{
if(alive&&e.key==='ArrowLeft')px=clamp(px-1);
if(alive&&e.key==='ArrowRight')px=clamp(px+1);
if(['1','2','3'].includes(e.key))start(+e.key);
if(e.key===' ')start();
draw();
});
start();
</script>操作は、左右キーで移動、1・2・3キーでモードを切り替えて最初からやり直し、スペースキーで同じモードのやり直しです。ゲームオーバーのたびに「モード番号,生存したtick数」が画面下に1行ずつ積み上がります。tick(ティック)とは、ゲームが1回進む単位のことで、ここでは400ミリ秒ごとです。
記録の取り方:何を、どの単位で、何回
画面に自動で出るログは生存tick数だけです。これに、遊ぶ人が手で足す記録が三つあります。1プレイごとに、次の4項目を紙か表計算に書きます。
- モード番号(1・2・3)
- 生存tick数(画面のログから転記します)
- 先読み度:「次の安全レーンが読めていた」が○、「ときどき読めた」が△、「読めなかった」が×
- 続行の意欲:終わった直後に「もう一度やりたい」か「今はいい」かを、プレイを始める前に決めた基準で一言書きます
回数の目安は、1モードあたり連続10プレイです。10回あれば、生存tickの推移と先読み度の変化が見えやすくなります。3モードで合計30プレイなので、途中で休憩を挟んでも1時間はかかりません。
順序の影響を避けるため、遊ぶ順番を入れ替えて2セット行います。1日目は1→2→3、別の日は3→2→1とします。最初に遊ぶモードは何も知らない状態で遊べる分だけ有利な条件になるので、順序を変えないと、その影響を比べられません。
さらに可能なら、同じ手順を3〜5人程度に頼みます。作者本人はルールの式を知っているため、先読み度が高く出ます。他の人に遊んでもらうときは、式の内容を伝えず、「安全な道は一本だけで、通れるのは常に1マス以内の移動です」とだけ説明します。
結果の読み方:どうなればどう判断するか
数字が揃ったら、モードごとに生存tickの推移を折れ線にします。そのうえで、先読み度と続行の意欲を重ねます。判断の目安は次のとおりです。
- 生存tickが伸びて頭打ちになり、先読み度が○で揃う:パターンを覚え切った状態です。この後に続行の意欲が「やりたい」のまま続くなら、覚え切った後にも別の楽しみがあります。意欲が「今はいい」に変わるなら、解き終わりによる飽きです。変わった回を記録しておくと、「飽きの来る回数」の目安になります。
- 生存tickが伸びず、先読み度が×のまま続く:覚えられていません。ここで続行の意欲が下がるなら、単調さによる飽きや、ルールの読みにくさが疑われます。
- 生存tickは伸びないが、続行の意欲は高い:まだ解けていないことが、そのまま動機になっている状態です。ルールを把握しようとする試行が続いているかを、数回のプレイで確認します。
- モード3でプレイごとのばらつきが大きい:入力が展開を変えている可能性があります。ばらつきが大きいのに先読み度が低くないなら、「読めるのに毎回違う」状態で、決定的ルールの利点が最も出ていると考えられます。
次に、3モードを横に並べて比べます。モード1とモード2の違いは、周期の長さがどれだけ覚える回数に影響するかを示します。モード3との違いは、入力への反応が「覚えて終わり」を遅らせるかどうかを示します。
どのモードも、頭打ちが早く、意欲もすぐ落ちる結果になる可能性もあります。その場合は、この試作の変化の源が足りていないという結論になります。それも立派な答えです。足りない要素を次の試作で足せばよいからです。
起こりやすい誤り
この実験は小さい分、結果をゆがめる落とし穴がいくつかあります。
- 周期が見かけと実際で違う:差の列が8行周期でも、端のレーンで値が詰まる処理(clamp)が入るため、安全レーンの位置の並びは別の周期を持ちえます。式の周期ではなく、実際に出る位置の並びを最初の20行ほど書き出して、本当の繰り返しの長さを確かめます。
- 見える行数が答えを左右する:画面に6行見えているので、5行先まで読めます。パターンを覚えなくても、見えている範囲だけで解ける場合があります。生存tickが伸びても「覚えた」のか「見えている」だけなのかは、行数Hを3や4に減らして比べないと区別できません。
- 作者の記憶が混ざる:式を書いた本人の先読み度は、他の人より高くなりがちです。作者の記録は参考にとどめ、判断には他の人の記録を重く見ます。
- 初回プレイの新鮮さ:最初の1〜2回は、どのモードでも物珍しさで意欲が高く出ます。判断には3回目以降の傾向を使います。
- タブの切り替え:ブラウザは、非表示のタブでタイマーの動きを遅くすることがあります。遊んでいる間は、このタブを前面に出したままにします。
- 操作の遅れを難度と取り違える:400ミリ秒の間隔は、人や環境によって速すぎることがあります。ゲームオーバーが極端に早い場合は、ルールではなく速度の問題かもしれません。その場合は間隔の数値を変えて、全モード同じ条件にそろえてから比べます。
結果を設計に戻す:次に変える一点
実験の結果が出たら、飽きの種類に応じて、変えるのは一度に一点だけにします。複数を同時に変えると、どれが効いたのか分からなくなります。
解き終わりによる飽きが見えたなら、候補は三つあります。周期の長いルールを重ねること、プレイヤーの入力を展開に混ぜること、見える先読み行数を減らして覚える負荷を上げることです。単調さによる飽きが見えたなら、数行ごとに見た目や安全レーンの幅に変化をつける方向が考えられます。
どの方向でも、乱数を使わない利点は残ります。同じ条件で再挑戦でき、失敗の原因を自分の操作に求められる点です。アソバースの公式作品「かたちのあいだ」には、同じ配置の再挑戦があります。乱数の有無は作品を開いて確かめることになりますが、同じ配置に戻ったときに何が遊びを保つのかを観察する参考になります。
次の行動
まず上のコードを index.html として保存し、3モードを10回ずつ遊んで、先読み度と続行の意欲を記録してみてください。結果を見て、変える一点を決め、次の版を作ります。作品として形になったら、index.html を入口にしたHTMLかZIPでアソバースに投稿できます。紹介文には、どのルールで変化を作っているかを一言添えると、遊ぶ人が仕組みに気づく手がかりになります。
