AOYAMA Koji's プログラミングブログ - プログラミングを楽しく体験

【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】プログラマー向け実装解説

2025/08/17
【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】

 自動生成☆詰将棋Webアプリでは、 通常2次元で扱う将棋盤の座標系を、1次元で扱うことで、高速化に成功しました。 グリッド型の盤面を持つ他のゲーム処理にも応用できますので、その詳細を解説します。
 なお自動生成☆詰将棋Webアプリは、この記事などでお楽しみください。

プログラミングブログ記事一覧



[PR]

グリッド型盤面の2次元座標処理


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 将棋盤2次元座標
 グリッド型盤面、すなわち格子状の盤面を持つゲームの処理は、図のように2次元座標系で表すのが一般的でしょう。
 実際のマスとの対応がわかりやすく、特に不都合がなければ、このまま進めて問題ありません。

2次元座標盤面の駒の移動


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 2次元座標系将棋盤における桂の動き
 ここで、2次元座標盤面の駒の動きを確認します。
 例えば▲桂は (-1,-2) と (1,-2) に動けます。 図の▲5五桂は、2次元座標盤面では (4,4) にいますが、そこから (3,2) と (5,2) に動けます。
(4,4) + (-1,-2) → (3,2)
(4,4) + ( 1,-2) → (5,2)

 この処理を実装するためには、1箇所動かすために、以下のように加算が2回必要です
//**********************************************************************
// ( iPosX , iPosY ) .. 駒の現在位置
// ( iDirX , iDirY ) .. 駒の移動方向
//**********************************************************************
iPosX += iDirX;
iPosY += iDirY;

パフォーマンス要求


 詰手順探索プログラムでは、王手かどうかの判定が多く、各駒が移動できるマスの判定プログラム速度にパフォーマンスが要求されます。
 それを実現するために、盤面を1次元として、駒移動の加算処理を2回から1回にする挑戦をして、成功した実践例の紹介が、本記事の主旨です。

将棋盤の1次元座標処理


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 将棋盤1次元座標余白あり実践例
 結論として、自動生成☆詰将棋では、この図のように、1列余白がある1次元座標の将棋盤を用いることにしました。
 前述の2次元座標を (x,y) の場合、この1次元座標は y*10 + x で求められます。
[PR]

駒の移動処理


 1次元座標系将棋盤(実践例)のコマの移動を、短距離と長距離に分けて、解説します。
 どちらも、1歩移動するのに加算は1回です。 2次元座標系ですと2回必要ですので、この部分の速度パフォーマンスは単純に2倍です。

短距離の移動


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 1次元座標桂将棋盤(実践例)の短距離駒移動
 短距離移動は、ここでは、1歩だけ動く駒移動のことです。 玉・金・銀・桂・歩、成銀・成桂・成香・と、および、龍の斜め、馬の前後左右が該当します。
 例えば▲桂は、図のように -21 と -19 に動けます。 プログラムは以下の形です。
//**********************************************************************
// 短距離移動
// iSquareXY .. 駒の現在位置
// iDirXY    .. 駒の移動方向
//**********************************************************************
iSquareXY += iDirXY;

長距離の移動


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 1次元座標桂将棋盤(実践例)の長距離駒移動
 長距離移動は、複数歩動く駒移動のことです。 香・飛・角、および、龍の前後左右、馬の斜め、が該当します。
 例えば▲香は、図のように -10 に繰り返し移動できます。
 途中に味方駒があるとそこに行けず、 敵駒があるとそこまでしか行けません。 そのため、駒の元の位置から順番に移動させて処理します。
 プログラムは以下です。
//**********************************************************************
// 長距離移動
// iSquareXY .. 駒の現在位置
// iDirXY    .. 駒の移動方向
//**********************************************************************
iSquareXY += iDirXY;
while ( 盤内?( iSquareXY ) ) {
  <各種処理>
  駒あり?( iSquareXY ) break;
  iSquareXY += iDirXY;
}

盤の内外判定


 盤の内外を判定する処理は、前述の長距離移動のプログラムの中にも登場する通り、詰手順探索プログラムでは頻繁に呼ばれます。 そのため高速に実装する必要があります。
 盤の内外判定の方法は、上下と左右で異なります。

上下の盤内外判定


 上下の盤外判定は、1次元座標の数値比較で行います。
 0以上88以下であれば盤内と判定できます。

左右の盤内外判定


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 1次元座標桂将棋盤(実践例)の盤外判定
 左右の盤内外判定が高速であることが、この1次元座標系将棋盤の自慢です。

判定式

 今の1次元座標を 10 で割った余りが 9 なら盤外、それ以外なら盤内と判断できます。
 理由はこの図の通り、横10列のうち最後の [9],[19],...,[89] を余白としているからです。

必要条件

 この判定式は、2列以上はみ出ると使えません。
 例えば2次元座標が盤外の (-2,2) の場合に 1次元座標に変換すると (-2)*10 + 2 → 18 になり、盤内と判定されます。
 しかし、例えば飛車がこの座標に動くためにはその前に (-1,2) を通ります。 変換すると 19 のためこの時点盤外と判定され、処理が終了します。 そのため (-2,2) に到達することはありません。
 つまり最大1列しかはみ出ないことが必要条件ですが、 長距離移動を含めて、プログラムで扱う左右方向の移動は1歩ずつのため、 実践的にはまったく問題ありません。

盤外判定のプログラム


 盤外判定をまとめると、以下のプログラムになります。
//**********************************************************************
// 盤内判定
// iSquareXY .. 駒の現在位置
//**********************************************************************
return ( 0 <= iSquareXY && iSquareXY <= 88 
         && 9 !== iSquareXY % 10 );

1次元座標将棋盤の盤外判定の失敗例


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 1次元座標系将棋盤(失敗例)における盤外判定
 参考までに、もし余白が無いと、このように、盤外判定されるべき座標が、左右反対側の位置に存在します。
 これは1次元座標だけでは盤内判断が難しく、2次元座標に戻せば可能ですが、パフォーマンスの問題が生じるため、失敗です。
 前述の通り、これは、1列の余白を設けることで解決しています。

王手判定の高速化


 将棋盤を1次元座標系にすることで、王手判定も高速化できます。 短距離と長距離、それぞれ工夫しています。

短距離の王手判定


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 短距離の王手判定
 短距離の王手判定は、まず、後手玉と、対象の先手駒の座標の差分を取得します。
 そして、その差分が、対象の先手の駒の動ける方向の配列の要素に含まれれば、王手がかかっています。
 この図の例では、後手玉の1次元座標から、▲桂の1次元座標を引くと -21 となります。 ▲桂馬は短距離の -19 と -21 に動けることを、配列として保持しています。
 座標の差分 (-21) が、短距離移動の配列 [-19,-21] の要素に含まれますので、この状態は王手がかかっていると判定されます。
 なお実践は、JavaScript特有の方法として、事前に Set オブジェクトすることで、要素に含まれるかどうかを has() で高速に判定できるようにしています。
//**********************************************************************
// 王手判定用事前準備
//**********************************************************************
// 短距離移動方向:▲桂
aaiMOVEMENT_SHORT[ iPIECE_FIRST_KEI ] = new Set( [ -19 , -21 ] );

//**********************************************************************
// 王手判定
//  iGOTEGYOKUSquareXY .. 後手玉の1次元座標
//  iPiece             .. 対象の駒種
//  iSquareXY          .. 対象の1次元座標
//**********************************************************************
function checkOUTE( iGOTEGYOKUSquareXY , iPiece , iSquareXY ) {
  // 差分取得
  let iDiffXY = iGOTEGYOKUSquareXY - iSquareXY;
  // 短距離判定
  if ( aaiMOVEMENT_SHORT[ iPiece ].has( iDiffXY ) ) return true;
  // 長距離判定
  //(略)
}

長距離の王手判定


【技術解説】将棋盤処理で実践:1次元座標変換による高速化【自動生成☆詰将棋】 長距離の王手判定
 長距離の王手判定は、まず、後手玉と、対象の先手駒の座標の差分を取得します。
 そして、対象の先手の駒の動ける方向のうち、その差分と符号が同じで、距離が近く、割り切れるものがあれば、王手がかかっている可能性があります。
 途中に別の駒がある場合も考えられるので、王手がかかっている可能性がある場合のみ、1歩ずつ動かして調べ、実際に王手がかかっているかを判定します。
 この図の例では、後手玉の1次元座標から、▲角の1次元座標を引くと -33 となります。
 ▲角は長距離の -11,-9,9,11 に動けます。同じ符号なのは -11 と -9 で、-11 で -33 を割り切れ、3歩動けば到達することがわかります。
 その後は1歩ずつ動かして調べる必要があります。 本件は、王手がかかっていない確率が圧倒的に高いので、可能ならできるだけ早い段階で候補から除外し、1歩ずつ動かして調べる処理を少なくすることが、最適化の重要な要素になります。
//**********************************************************************
// 王手判定用事前準備
//**********************************************************************
// 長距離移動方向:▲角
aaiMOVEMENT_LONG[ iPIECE_FIRST_KAKU ] = [ -11 , -9 , 9 , 11 ];
// 再長距離の定義
const iPOSITION_SQUARE_XY_MOVE_MAX = 9;

//**********************************************************************
// 王手判定
//  iGOTEGYOKUSquareXY .. 後手玉の1次元座標
//  iPiece             .. 対象の駒種
//  iSquareXY          .. 対象の1次元座標
//**********************************************************************
function checkOUTE( iGOTEGYOKUSquareXY , iPiece , iSquareXY ) {
  // 差分取得
  let iDiffXY = iGOTEGYOKUSquareXY - iSquareXY;
  // 短距離判定
  //(略)※短距離で王手がかかっっていない場合のみ以下へ
  // 長距離判定
  for ( let iDirXY of aaiMOVEMENT_LONG[ iPiece ] ) {
    // 同じ符号か確認
    if ( iDiffXY * iDirXY < 0 ) continue; 
    // 割り切れるか確認※負の剰余仕様は言語で異なるので注意
    if ( 0 !== iDiffXY % iDirXY ) continue;
    // 距離を確認
    if ( iPOSITION_SQUARE_XY_MOVE_MAX <= iDiffXY / iDirXY  ) continue;
    // 1歩ずつ確認
    let iTmpXY = iSquareXY + iDirXY;
    while ( 盤内?( iTmpXY ) ) {
      // 玉があれば王手
      if ( iTmpXY === iGOTEGYOKUSquareXY ) return true;
      // 玉以外の駒があれば終了
      if ( 駒あり?( iTmpXY ) ) break;
      // 1歩進む
      iTmpXY += iDirXY;
    }
  }
  // この駒では王手していない
  return false;
}

移動方向情報の1次元座標系変換


 前述の通り、1次元座標で扱うことで、速度パフォーマンスが高く実装できます。
 しかし、移動方向情報、例えば▲桂の [ -19 , -21 ] について、パッと見ではどこに移動する情報かがわかりづらいという問題があります。

直感に従わないデータは不具合に繋がる


 正しく入力できれば問題ありませんが、直感に沿わないデータはミスに気づきづらく、不具合の原因になりやすいです。

直感的なデータで設定し効率的なデータに変換


 そこで自動生成☆詰将棋入力では、直感的な2次元座標で方向情報を入力して、効率的な1次元座標に変換することにしました。
 具体的には、例えば桂なら以下の情報設定を行います。
// 短距離移動方向:▲桂
aaaiMOVEMENT_SHORT[ iPIECE_FIRST_KEI ] = [ [-1,-2],[1,-2] ];

 そして、このデータから ▲桂の [ -21 , -19 ] と、▽桂の [ 21 , 19 ] を、初期に計算して、動的に生成しています。

実装された方向情報


 以下が、自動生成☆詰将棋の盤面処理のために実装された方向情報です。
 関数 getMovementAsPosition にて、それぞれ 1次座標系に変換し Set オブジェクト化しています。
// 金
const aaiMOVEMENT_KIN = [ [-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[0,1] ];
// 短距離
const aaaiMOVEMENT_SHORT = [
  [ [0,-1] ],                   // FU
  [],                           // KYO
  [ [-1,-2],[1,-2] ],           // KEI
  [ [-1,-1],[0,-1],[1,-1],[-1,1],[1,1] ], // GIN
  aaiMOVEMENT_KIN,              // KIN
  [],                           // KAKU
  [],                           // HISHA
  [ [1,-1],[0,-1],[-1,-1],[1,0],[1,1],[-1,0],[0,1],[-1,1] ], // GYOKU
  aaiMOVEMENT_KIN,              // TO-KIN
  aaiMOVEMENT_KIN,              // NARI-KYO
  aaiMOVEMENT_KIN,              // NARI-KEI
  aaiMOVEMENT_KIN,              // NARI-GIN
  [],
  [ [0,-1],[-1,0],[1,0],[0,1] ],   // UMA
  [ [-1,-1],[1,-1],[-1,1],[1,1] ], // RYU
  [],                           // MUDAAI
  ];
// 長距離
const aaaiMOVEMENT_LONG = [
  [],                           // FU
  [ [0,-1] ],                   // KYO
  [],                           // KEI
  [],                           // GIN
  [],                           // KIN
  [ [-1,-1],[1,-1],[-1,1],[1,1] ], // KAKU
  [ [0,-1],[-1,0],[1,0],[0,1] ],   // HISHA
  [],                           // GYOKU
  [],                           // TO-KIN
  [],                           // NARI-KYO
  [],                           // NARI-KEI
  [],                           // NARI-GIN
  [],
  [ [-1,-1],[1,-1],[-1,1],[1,1] ], // UMA
  [ [0,-1],[-1,0],[1,0],[0,1] ],   // RYU
  [],                           // MUDAAI
  ];
// プログラムで使用するデータ
export const aaiMOVEMENT_SHORT = getMovementAsPosition( aaaiMOVEMENT_SHORT );
export const aaiMOVEMENT_LONG  = getMovementAsPosition( aaaiMOVEMENT_LONG );

グリッド型盤面への応用について


 以上が、将棋盤の1次元座標処理の解説です。
 この手法は、他のグリッド型の盤面でプレイするゲームにも応用できると思います。
 ただし、チェスのナイトのように、1歩で横に2マス動く駒がある場合は、盤外判定には工夫が必要です。
 またチェスやリバーシ(商品名としてオセロが有名)のように、8x8 という、コンピューターが得意な2の乗数からなる盤面は、さらに工夫の余地があるかもしれません。 筆者がわかっているわけではありませんが、考えるのは楽しそうです。
[PR]

まとめ


 本記事では、将棋盤を1次元座標として扱い、速度パフォーマンスを向上させた実装を解説しました。
 従来の2次元座標処理に比べて、移動処理、判定処理が高速になりました。 特に短距離移動に対する王手判定は、ループを伴わない実装で、大幅な高速化を実現できています。
 その中で、移動方向情報は2次元で設定するなど、プログラムのメンテナンス性にもある程度配慮した、賢く実践的な高速化実装になっていると自負しています。
 この技術は、グリッド型の盤面を持つゲームへの応用も可能だと思いますので、この記事が、少しでも皆さまの参考になりましたら幸いです。

補足

  • プログラムソースコード例はすべて JavaScript です。実際に使用したコードを見やすく切り出して説明を追加しています。
  • 記事の校正/添削に生成AIの Anthropic Claude を利用しております。
  • 画像内のラスタライズ文字フォントにOpen Font LicenseNoto Sans Japaneseを使用しております。
  • 画像内のラスタライズ文字フォントにOpen Font LicenseNoto Sans Monoを使用しております。

カテゴリー:自動生成☆詰将棋,プログラミング解説
[PR]