マガジン

オセロの盤面は何通りあるか:数えられない数と、数えられる数

遊びを見つける

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

チェスの候補手を小さな灯りで示し、一手前へ戻る糸を添えた写真風イメージ。
記事のテーマをもとに制作したAI生成イラスト。

オセロの盤面は何通りあるのか。この問いには、厳密な答えより先に、上限を出す方法があります。64マスのそれぞれが「空・黒・白」の3つの状態を取り得ると考えて、3を64回かけます。結果はおよそ3.4×10の30乗で、1のあとに0が30個並ぶ数です。この数は、紙とペンがあれば自分で確かめられます。 ただし、この数は実際の対局で現れる盤面の数ではありません。中央の4マスは最初から埋まっていますし、石は1手ごとに1個ずつ増えます。ルール上たどり着けない配置も大量に含まれています。この記事では、現実に到達できる局面の数を身近な時間に置き換えて感じ取ります。そのうえで、序盤だけは数え切れることを、短いプログラムで自分で確かめる手順にします。

この記事の目次
  1. 上限は掛け算で出せます
  2. 現実の局面数を、身近な時間に置き換えます
  3. 数えられる範囲:手順の数と局面の数は別物です
  4. 自分で数える設計書
  5. 公式作品から読み取れること
  6. 次の行動

上限は掛け算で出せます

3の累乗は、次のように手計算でも追えます。

  • 3の10乗は59,049です。
  • 3の20乗はおよそ35億です(3,486,784,401)。
  • 3の40乗は、その2乗でおよそ1.2×10の19乗です。
  • 3の60乗はおよそ4.2×10の28乗で、64乗にするにはさらに81倍します。

この3.4×10の30乗は、「どんな盤面もあり得ると仮定した上限」です。現実の局面がこれより大幅に少なくなる理由は、次の3つです。

  • 中央4マスは必ず埋まっているので、「全部が空の盤面」は存在しません。
  • 石の数は「4個+置いた手数」で決まり、最大でも64個です。石が3個以下の盤面にはなりません。
  • 返すルールがあるため、石が置ける順序では作れない配置があります。

つまり、上限より現実の数は小さくなります。ただし、どれだけ小さいかは掛け算では出せません。

現実の局面数を、身近な時間に置き換えます

到達できる局面の数については、研究者による見積もりが公開されています。一般に知られている範囲では、10の28乗前後という桁です。厳密に数え切った値ではなく、見積もりである点には注意してください。公開されている解析で、手法と数字を直接確認することをおすすめします。

ここでは、10の28乗が正しいものと仮定して、身近な時間に置き換えてみます。

  1. コンピューターが1秒に10億(10の9乗)個の局面を数えられるとします。
  2. 10の28乗を10の9乗で割ると、10の19乗秒です。
  3. 1年はおよそ3.2×10の7乗秒なので、年に直すと約3,000億年です。
  4. 宇宙の年齢は約138億年と言われます。3,000億年はその約23倍です。

1秒に10億局面という速さは、日常の感覚ではとてつもない速さです。それでも、全局面を1つずつ数えるには宇宙の年齢の20倍以上かかる計算になります。これが「数えられない数」の実感です。

さらに大きい数もあります。局面ではなく「手順」を数えた、ゲーム木と呼ばれる数です。ゲーム木とは、初期配置から「この手の次はこの手」と枝分かれしていく手順全体のことで、10の58乗前後と見積もられていると言われています(こちらも公開されている解析で確認してください)。10の28乗でさえ数えられないのに、その右にさらに0が30個並ぶ規模です。

この桁感は、仮定の掛け算でも確かめられます。1手あたりの選択肢が平均10通りだと仮定すると、60手で10の60乗です。実際の平均は序盤と終盤で違うので、これはあくまで桁を感じるための仮定です。「毎手10通り前後」という素朴な見積もりが、10の58乗と同じ桁に近づくことは、手計算で確認できます。

数えられる範囲:手順の数と局面の数は別物です

一方で、序盤なら人間にも数えられます。黒が先手の初期配置では、黒の合法手は4通りです。たとえば黒がd3に置いたとします。盤上には、d3・d4・d5・e4が黒、e5が白という状態が残ります。ここで白が置けるのは、c3・e3・c5の3通りです。それぞれ、斜め・縦・横の1方向で黒をはさめるからです。他の3つの初手も、盤を回転・反転させれば同じ形なので、2手目も各3通りになります。したがって、2手後までの手順は4×3の12通りです。

ここで、2つの数を区別しておきます。

  • 手順の数:どの順番で置いたかまで区別して数えた数です。
  • 局面の数:結果として盤面が同じなら1つと数える数です。

別々の手順が同じ盤面にたどり着くことを、転置と呼びます。転置が起きると、手順の数は局面の数より多くなります。ここに規模の秘密があります。手順の数は、ゲーム木のように爆発的に増えます。局面の数は転置のぶんだけ抑えられますが、それでも爆発は止まりません。

転置がどの手数から現れるか、その差がどのくらい開いていくかは、実際に数えて確かめる問いとして使えます。ここでは結果を書かず、次の節で数え方を示します。

自分で数える設計書

問いは「序盤の手順の数と局面の数は、手数が進むとどう開いていくか」です。以下のコードをHTMLファイルとして保存し、ブラウザで開けば動きます。外部ライブラリは不要です。黒を1、白を−1、先手は黒とし、パスは深さを消費しないものとして扱っています。

html
<pre id="o"></pre>
<script>
const D=[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
const ok=(y,x)=>y>=0&&y<8&&x>=0&&x<8;
function start(){const b=Array(64).fill(0);b[27]=b[36]=-1;b[28]=b[35]=1;return b;}
function flips(b,i,p){
  if(b[i]) return [];
  const r=i>>3, c=i&7, out=[];
  for(const [dr,dc] of D){
    const line=[]; let y=r+dr, x=c+dc;
    while(ok(y,x)&&b[y*8+x]===-p){line.push(y*8+x); y+=dr; x+=dc;}
    if(line.length&&ok(y,x)&&b[y*8+x]===p) out.push(...line);
  }
  return out;
}
function moves(b,p){
  const m=[];
  for(let i=0;i<64;i++){const f=flips(b,i,p); if(f.length) m.push([i,f]);}
  return m;
}
function play(b,p,i,f){const nb=b.slice(); nb[i]=p; for(const j of f) nb[j]=p; return nb;}
function paths(b,p,d){
  if(d===0) return 1;
  let m=moves(b,p);
  if(!m.length){p=-p; m=moves(b,p);}
  let n=0;
  for(const [i,f] of m) n+=paths(play(b,p,i,f),-p,d-1);
  return n;
}
function positions(depth){
  let layer=new Map([[start().join(',')+'|1',[start(),1]]]), res=[];
  for(let d=1;d<=depth;d++){
    const next=new Map();
    for(const [b,p] of layer.values())
      for(const [i,f] of moves(b,p)){
        const nb=play(b,p,i,f), q=moves(nb,-p).length?-p:p;
        next.set(nb.join(',')+'|'+q,[nb,q]);
      }
    layer=next; res.push(layer.size);
  }
  return res;
}
const MAX=7, P=positions(MAX), o=[];
for(let d=1;d<=MAX;d++) o.push(`${d}手後 手順 ${paths(start(),1,d)} / 局面 ${P[d-1]}`);
document.getElementById('o').textContent=o.join('\n');
</script>

記録の取り方

数え方は決まった手順なので、同じコードなら何度実行しても同じ結果になります。回数を重ねるより、検算を丁寧に行うほうが大切です。

  1. まず1手後と2手後の数を見ます。手順は4と12になっていなければなりません。違えば、初期配置の色か、石を返す処理にバグがあります。
  2. 次に、MAX を1から順に増やし、次の表を埋めます。
  • 1、手順の数:、局面の数:、手順÷局面:、前の手数からの倍率(手順):
  • 2、手順の数:、局面の数:、手順÷局面:、前の手数からの倍率(手順):
  • …、手順の数:、局面の数:、手順÷局面:、前の手数からの倍率(手順):

MAX は7から8あたりまでにしておきます。これを超えると処理が重くなり、ブラウザが固まることがあります。

結果の読み方

  • 手順÷局面が1のままの手数は、転置がまだ起きていない範囲です。1を超えた最初の手数が、転置の始まりです。
  • この比が手数とともに大きくなるなら、同じ盤面に別の道筋でたどり着く場合が増えていると読めます。
  • 倍率の列は、1手進むごとに何倍に増えるかを表します。倍率が数倍のまま保たれるなら、60手に向けて指数的に増えていくという感覚が得られます。手順の倍率と局面の倍率を比べると、どちらが爆発的に増えるかも見えてきます。
  • 倍率が途中で落ちる場合は、パスや盤の端の影響が出ていないか、コードの設定を見直してください。

起こりやすい誤り

  • 石を返す方向は8方向すべてを調べます。縦と横だけにすると、数が少なく出ます。
  • 初期配置の色を取り違えると、手順の数が最初から合いません。d4とe5が白、d5とe4が黒です。
  • 局面の重複を調べるときは、盤面だけでなく手番もキーに入れます。同じ盤面でも手番が違えば、別の局面だからです。

公式作品から読み取れること

アソバースの「潮目のオセロ」は、確認済みの機能として、ひとり対局に3段階の相手、返る石のプレビュー、理由付きヒント、一手戻すを備えています。オンライン対局では、置けない手番は自動でパスになり、手番・合法手・勝敗はサーバーで確認される仕様です。

ここから読み取れることがあります。これらの機能は、盤面ごとにルールから合法手を計算する形で成り立っています。10の28乗規模の局面を、あらかじめ表にして持っておくことはできません。自動パスも、手がない状況をその都度判定して処理しているということです。ヒントや3段階の相手の内部でどんな探索をしているかは、確認済み事項にないので、ここでは扱いません。この部分は、作品を開いて遊びながら、プレビューやヒントの出方から確かめてください。

「灯りのチェス」も参考になります。判定エンジンにchess.jsを同梱し、CPUの思考時間を約450ミリ秒に制限しています。チェスの局面数はオセロよりさらに大きく、すべてを読み切ることはできません。時間に上限を置く設計は、そうした規模を前提にしたものとして読めます。

次の行動

上のコードを保存して開き、1手後が4、2手後が12になることを確かめてください。そのうえで、MAX を1つずつ増やして表を埋め、手順と局面の差が開き始める手数を探してみましょう。表を眺めたあとで「潮目のオセロ」を開き、返る石のプレビューやヒントが、その場の盤面からどう計算されていそうかを考えながら1局遊ぶと、数の大きさと遊びの手触りがつながります。