将棋のルール

将棋のルールを*完全に*把握している人は意外に少ない。かく言う筆者も、将棋プログラムの作成に当たり、改めて文献にあたった口である。ここでは、日本将棋連盟発行の「将棋ガイドブック」に沿って完全なルールを説明する。

基本ルール

将棋は、二人で行なうゲームで、将棋を行なうことを「将棋を指す」「対局する」のように言う。また、将棋は交互に盤の上で駒を動かす「指し手」を指すことで対局が進む。

駒には、それぞれ、動かし方のルールがあり、そのルールに沿って駒を動かさねばならない。対局は、どちらかが負けを認めること=「投了する」か、指し手を指さなければいけない番のプレイヤーが合法的な指し手がなくなった時(この後に記述する、ルールに沿った指し手が一つもできない場合)、そのプレイヤーの負けとなる。

また、合法的でない手を指した場合、反則となり、負けとなる。通常、合法的な指し手がなくなるケースは、放置すると次に王を取りますよという手=「王手」をかけられて、どんな指し手を指しても王を取られてしまう状態になることで、これを「詰み」という。

例外的なケースとして、対局中に同一局面が4回現れた場合、「千日手」となる。千日手の場合、通常引き分けとなるが、後述する連続王手の千日手については、連続王手をかけている側の負けとなる。

また、双方の玉が相手陣内に入り(入玉という)、玉を0点、飛車角を5点、その他の駒を1点として数えた時、双方が24点以上持っている場合、「持将棋」となり、引き分けとなる。

駒の動かし方の原則

盤上の位置を表す符号

盤の縦方向を「筋」、横方向を「段」と呼ぶ。筋は右から「1、2、3…9筋」まで算用数字であらわす。段は、上から「一、二、三、…九段」までで、漢数字であらわす。

一つの升目を表すのに、たとえば3筋の7段目ならば、「3七」のように、算用数字と漢数字を組み合わせてあらわす。(図1)

図1

持ち駒の書かれる場所

持ち駒の有無および種類、その枚数は、図面の右側に自分の持ち駒、相手側から見た右側=左側に相手の持ち駒を記述する。持ち駒がない場合には、「なし」と記述する。(図2)

図2

自陣と相手陣

自分から見て手前の三段を自陣といい、向こう側の三段を相手陣と呼ぶ。(図3)自分の駒が相手陣に入ったときには、成る(駒を裏返す)ことが出来る。また、自分の駒が相手陣内からいずれかへ動くときにも成ることが出来る。

相手陣に一度入った駒であっても、相手陣から出たら、再度相手陣に入るまで成ることは出来ないので注意。

図3

駒の名称と動き方

玉将
ぎょくしょうと読む。略称「玉(ぎょく)」または「王(おう)」。王将と書く場合もあるし、今の駒では片方が玉将、片方が王将であることが多いが、元来は両方玉将であったと言われている。
縦横斜めの8方向、いずれにも1マス進むことが出来る。(図4)
図4
飛車
ひしゃと読む。略称は「飛」。
縦横に自由に進むことが出来るが、自分の駒、相手の駒を飛び越すことは出来ない。
ただし、相手の駒のあるマスに進んで、その駒を取ることは出来る。(図5)
図5
龍王
りゅうおうと読む。飛車の成った駒。略称は「龍」または「竜」。
飛車の動きに加えて、斜めに1マスだけ進むことが出来る。(図6)
図6
角行
かくぎょうと読む。略称は「角」。
斜めに自由に動くことが出来るが、飛車と同様に、自分の駒や相手の駒を飛び越すことは出来ない。(図7)
図7
龍馬
りゅうまと読む。略称は「馬」。桂馬とは関係がないので注意。
角の動きに加えて、縦横に1マス進むことが出来る。(図8)
図8
金将
きんしょうと読む。略称は「金」。
縦横に1マス動けるのに加えて、斜め前方にも動くことが出来る。(図9)
図9
銀将
ぎんしょうと読む。略称は「銀」。
斜め方向に1マス動けるのに加えて、前方にも1マス動くことが出来る。(図10)
図10
成り銀
金将と同じ動きが出来る。本書では、「成銀」の代わりに(1文字で駒を表す方が便利なので)「全」と表記する場合がある。
桂馬
けいまと読む。略称は「桂」。角の成り駒である「馬」と混同しないように注意。
動きはいわゆる桂馬飛び。たてに2マス進んでから、どちらか横に1マス動く。
この駒だけは、駒を飛び越えて動くことが出来る。(図11)
図11
成り桂
金将と同じ動きが出来る。本書では、「成桂」の代わりに「圭」と表記する場合がある。
香車
「きょうしゃ」と読む。略称は「香(きょう)」。
前方に一直線に自由に進める。
飛車と同様に、自分の駒や相手の駒を飛び越すことは出来ない。(図12)
図12
成り香
金将と同じ動きが出来る。本書では、「成香」の代わりに「杏」と表記する場合がある。
歩兵
「ふひょう」と読む。略称は「歩(ふ)」。
前に1マスだけ進むことが出来る。(図13)
図13
と金
歩が成った駒。金将と同じ動きが出来る。略称は「と」。

禁手

将棋には、いくつかルール上指してはいけない手が存在する。プログラム作成上、これらのルールは正確に把握しておかないとならない。

二歩
将棋を始めた人なら誰でも知っている初歩的な反則が二歩である。同じ筋に、自分の歩が二枚あるように指してはいけない。ただし、と金は歩ではないので、と金がある筋に歩を打つのは反則ではない。歩は横に動けないので、二歩は、歩を打つ時に生じる反則である。
駒の利きが盤上に存在しないようにする手
従来は、「行き所なき駒の禁」と言っていたが、具体的には、歩と香車は相手陣の1段目、桂馬は1段目と2段目に打つことは出来ない。また、上記のように駒を進める際には、必ず成らなければならない。
そうしないと、盤上に利きがない駒となるからである。(図14参照)
図14
打ち歩詰め
持ち駒の歩を打った状態で相手玉が詰む場合、その歩を打つ手は反則である。
ただし、歩を打ったその状態で詰む場合が反則なのであって、例えば、図15の場合、打った歩を金で一旦取ることが出来るので反則ではない。
一例を挙げると、▲2三歩△同金▲同桂右成で詰んでいるが、歩を打った手ではまだ詰んでいない。
図15
しかし、図16のように、打った歩を取れない場合には、反則となる。
図16
ただし、図17のように、元々盤上にいた歩を進める場合には、突き歩詰めといい、立派な詰みで、反則ではない。
図17
もう少し例をあげよう。図18の場合も、打った歩が取れないので反則である。打った歩を金で取ろうとすると、飛車の利きが玉に届くため、この状態で詰んでいる=打ち歩詰めなのである。
図18
コンピュータ将棋においては、なかなか判定が面倒な項目である。
玉を取られる着手
これは、王手の放置および自らの玉を王手にさらす手を言う。例えば、詰んでいるのにまだ逃げようとするような手は反則となるし、王手がかかっているのに放置するのも反則となる。
連続王手の千日手
後にプログラムの千日手の対応の際に詳しく解説するが、片方が王手をかけ続けて、同一局面が4回現れた場合、王手をかけ続けていた方が反則負けとなる。
これも、コンピュータ将棋においては、なかなか判定が面倒な項目である。
駒の動かし方の間違い
銀を横に動かすなどの駒の動かし方の間違いは反則で、負けとなる。珍しい例では、銀を裏返したまま盤面に打って反則負けしたプロ棋士がいる。ルール通りにさせるプログラムが完成したなら、あまりコンピュータが意識しないでよい反則である。

棋譜の表記法

プログラムを作るのに必ずしも必要なわけではないが、将棋の常識として覚えておきたい。

プロの棋譜を新聞などから入力する際にも、この常識を知らないと入力できないという事情もある。

①手の表し方
通常、移動後の駒の位置と、駒の種類で表す。例えば、将棋で多い初手、角道を開ける手は▲7六歩、飛車先を突く手は▲8六歩などのように表される。後手は△3四歩、△8四歩のようにあらわす。
②「同」
指した手の符号の位置が、直前の相手の指し手の符号の位置と同じ場合、符号の代わりに「同」を用いる。飛車先の歩交換を例にあげると、▲2四歩 △同歩 ▲同飛 のようになる。
③「成」と「不成」
駒が成る指し手の時には、その指し手の符号の最後に「成」とつける。これは、「なり」「なる」のように読む。
また、駒が成れる所を対局者が成らないことを選択した場合には、「不成」と表記する。これは、「ならず」と読む。
④「打」
持ち駒を打つ時に、そこに移動できる同種の駒がある場合、単に「▲5二金」と書いたのでは、金が動いて5二に移動したのか、駒を打ったのか区別することが出来ない。そこで、単に「▲5二金」と書いた場合には、盤上の駒が移動したものとし、「▲5二金打」とした場合には、持ち駒の金を打ったものとする。
ただし、持ち駒の金を打つ場合でも、移動と混同する恐れのない場合(そこに移動できる金がいない場合)単に「▲5二金」のように記述し、「打」は省略される。
⑤「上」「引」「寄」
図19の場合に、「5八金」と書いたのでは、4八の金と6九の金のどちらが5八に動いたのか区別できない。このような場合には、まず、駒の動作を棋譜に表記することで区別する。
  1. 駒を上方向に動かす場合、「上」(あがる)と記述する。
  2. 駒を下方向に動かす場合、「引」(ひく)と記述する。
  3. 駒を横方向に動かす場合、「寄」(よる)と記述する。
図19の場合、4八の金が5八に動いたなら、「5八金寄」、6九の金が5八に動いたなら、「5八金上」と表記することになる。
図19
⑥「右」「左」
図20の場合、「5八金」と書いたのでは、4九の金と6九の金のどちらが5八に動いたのか区別できない。また、⑤のように、動作で記述することもできない(どちらも5八金上になってしまう)。
このような場合、駒を動かす側から見て右の駒を動かす場合、「右」(みぎ)、左の駒を動かす場合「左」(ひだり)と表記する。
図20の場合、4九の金が5八に動いたなら、「5八金右」、6九の金が5八に動いたなら、「5八金左」と表記することになる。
図20
⑦ ⑤、⑥の併記
金銀及び成桂、成香は、同じ位置に移動可能な駒が4枚存在する可能性があり、と金に至っては6枚存在することがあるため、⑤、⑥の方法を併記する必要がある場合がある。
また、原則として、省略可能な場合は出来るだけ省略を行う。例えば、図19の場合、5八金左上とは書かずに単に5八金上と書く。これは、5八に上がって移動できる「金」は5八の金一枚のみだからである。
⑧「直」
「上」の特殊ケースとして、まっすぐ上に上がる手を「直」と表記する場合がある。これは、図21のようなケースの場合に、「上」と表記したのでは、どの駒が動いたのか分からず、右、左との組み合わせでも処理できないからである。 この図の場合、4八→5八の移動を「5八と右寄」、6八→5八の移動を「5八と左寄」、5七→5八の移動を「5八と引」、 4九→5八の移動を「5八と右上」、5九→5八の移動を「5八と直」、6九→5八の移動を「5八と左上」と表記する。
「直」で表記するのは、図21や図22のように3枚以上の駒が入り組んでいる場合だけである。
図21
図22

2015年10月追記:連盟から出ている棋譜を見ると、入り組んだ場合だけではなく、単にまっすぐ上に進む手で、二つ以上の駒が移動可能な場合には、必ず「直」と表記しているようである。

なお、本書のプログラムでは、簡単のため、棋譜の表記法に従う代わりに、手の表示の際に、移動先の他に移動元を併記することにし、駒打ちの際には必ず「打」を表記するものとした。

これは、局面を参照しないと手の表示が出来なくなるという面倒な自体を避けるための処置である。もちろん、手が指される時の局面を参照すれば、正しい棋譜表記に対応することも可能なので、腕に自信のある方は挑戦してみて欲しい。

ルール通りに指すためのプログラム

まず、各駒をどうあらわすか決めてみよう。 各駒は、向き(上向き・下向き)と駒の種類(成り・不成りを含む)を持っている。 このとき、成りや、自分の駒であるかどうかを判定するのにビット演算が使えると高速で便利である。そのため、以下のように駒を定義した。 こうしてみると、意外にも将棋の駒の種類はビット演算と相性が良い。
なにもないところ 0
歩  1
香車 2
桂馬 3
銀  4
金  5
角  6
飛車 7
玉  8
と  9(1+8)
成香 10(2+8)
成桂 11(3+8)
成銀 12(4+8)
馬  14(6+8)
龍  15(7+8)

上向き(味方)の駒につける目印 16
下向き(敵)の駒につける目印  32

ソースで表すと、以下のようになる。

enum {
  // 何もないところ
  EMPTY=0,
  EMP=0,        // 3文字も準備しておくとソースが見やすいので(笑)
  // 成り駒につける目印(1ビット)
  PROMOTED=1<<3,

  // 駒をあらわす数値
  FU=1,
  KY=2,
  KE=3,
  GI=4,
  KI=5,
  KA=6,
  HI=7,
  OU=8,
  TO=FU+PROMOTED,
  NY=KY+PROMOTED,
  NK=KE+PROMOTED,
  NG=GI+PROMOTED,
  UM=KA+PROMOTED,
  RY=HI+PROMOTED,
  
  // 自分自身の駒につける目印(1ビット)
  SELF=1<<4,
  // 敵の駒につける目印(1ビット)
  ENEMY=1<<5,
  // 敵も味方も進めないところ(盤の外)の目印
  WALL=SELF+ENEMY,

  // 実際の駒
  SFU=SELF+FU,  //味方の歩
  STO=SELF+TO,  //味方のと金
  SKY=SELF+KY,  //味方の香車
  SNY=SELF+NY,  //味方の成り香
  SKE=SELF+KE,  //味方の桂馬
  SNK=SELF+NK,  //味方の成り桂
  SGI=SELF+GI,  //味方の銀
  SNG=SELF+NG,  //味方の成り銀
  SKI=SELF+KI,  //味方の金
  SKA=SELF+KA,  //味方の角
  SUM=SELF+UM,  //味方の馬
  SHI=SELF+HI,  //味方の飛車
  SRY=SELF+RY,  //味方の龍
  SOU=SELF+OU,  //味方の玉

  EFU=ENEMY+FU,  //敵の歩
  ETO=ENEMY+TO,  //敵のと金
  EKY=ENEMY+KY,  //敵の香車
  ENY=ENEMY+NY,  //敵の成り香
  EKE=ENEMY+KE,  //敵の桂馬
  ENK=ENEMY+NK,  //敵の成り桂
  EGI=ENEMY+GI,  //敵の銀
  ENG=ENEMY+NG,  //敵の成り銀
  EKI=ENEMY+KI,  //敵の金
  EKA=ENEMY+KA,  //敵の角
  EUM=ENEMY+UM,  //敵の馬
  EHI=ENEMY+HI,  //敵の飛車
  ERY=ENEMY+RY,  //敵の龍
  EOU=ENEMY+OU,  //敵の玉
};

また、駒自体は1バイトで表記可能なため、KomaInfという型を以下のように定義した。

typedef unsigned char KomaInf;

次に局面であるが、局面は盤面の状態と持ち駒の状態とからなる。(正確には、千日手などへの対応のため、過去の情報が必要であるが、簡単のため、ここでは触れないことにする。)

盤面は、2次元配列で表すのが簡単であるが、高速化のためには、1次元配列として、演算としては*16など2の階乗倍が使えることが望ましい。もう一つ、1次元配列とすることのメリットであるが、方向や移動を一つの数字で表せる(2次元配列なら筋と段の両方を持ったり、斜めに動くときには筋が-1、段が-1のようにもたなければならない。これが1次元配列なら、位置は一つの数字で表せるし、上記のように斜めに動く場合は、16*-1+(-1)=-17という一つの数字で表すことが出来る。)また、盤外として、盤外のコードが書き込まれていると処理の上で都合がよい。そこで、盤面は以下のように表すものとした。

KomaInf banpadding[16];
KomaInf ban[16*(9+2)];

banpaddingは、桂馬の利きが盤外のさらに外にはみ出すことを考慮して設けてある。banpaddingの中は、常にWALLである。

持ち駒は、自分の駒、相手の駒がそれぞれ何枚あるかを表す1次元配列とした。1次元配列は、最大で相手の飛車までが持ち駒となるので、大きさはEHI+1までが必要である。(成り駒がそのまま持ち駒になることはない。また、王を持ち駒にすることもない。王を取れる局面は、反則により自分の勝ちの局面である。)例を挙げると、Hand[SHI]が自分の飛車の持ち駒の枚数、Hand[EKI]が相手の金の持ち駒の枚数である。

int Hand[EHI+1];

以上は、グローバル変数とせず、Kyokumenクラスのメンバーとした。

次に、各駒の動きを、通常の動き(隣接したマスへの移動)と飛び(隣接したマスおよびさらにその向こうへ飛んで動ける移動)とに分類した。なお、桂馬飛びは、通常の動き(隣接したマスへの移動)と同様に扱う。これは、桂馬の動きは、途中の駒により遮られることがないからである。

動きとしては、8方向+桂馬による4方向で合計12方向が必要であることがわかる。

12方向を表にしたものが以下である。(この「方向」を1つの数字であらわせることが先も述べたように1次元配列にすることのメリットの一つである。)

// 方向を示す定数。
int Direct[12]={
  17,1,-15,16,-16,15,-1,-17,14,-18,18,-14
};

さらに、これを各駒の動きの表にしたものが以下となる。

// その方向に動けるか?その方向に飛んで動くものは入れてはいけない。
int CanMove[12][64]={
  {
  // Direct[0]=17,
  // |/
  //   ̄
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=1,
  // ↓
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,1,0,0,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-15,
  // \|
  //  ̄
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=16,
  // ←
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-16,
  // →
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=15,
  //  __
  // |\
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-1,
  // ↑
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1,1,1,1,1,1,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-17
  // __
  // /|
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1,1,1,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=14,先手の桂馬
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-18,先手の桂馬
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-14,後手の桂馬
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=18,後手の桂馬
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  }
};

// その方向に飛んで動くことが出来るか?
// 飛車角香車と龍と馬しかそういう駒はない
int CanJump[12][64]={
  // Direct[0]=17,
  // \|
  //  ̄
  {
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=1,
  // ↓
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-15,
  // \|
  //  ̄
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=16,
  // ←
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-16,
  // →
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=15,
  //  __
  // |\
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-1,
  // ↑
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-17
  // __
  // /|
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=14,先手の桂馬飛び
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-18,先手の桂馬飛び
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=-14,後手の桂馬飛び
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  },{
  // Direct[0]=18,後手の桂馬飛び
//  空空空空空空空空空空空空空空空空空歩香桂銀金角飛王と杏圭全金馬龍
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
//  空歩香桂銀金角飛王と杏圭全金馬龍壁空空空空空空空空空空空空空空空
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  }
};

非常に大きな表となったが、複雑なのはさらにこれからである。二歩や打ち歩詰め、その他の禁手を指さないように注意深くプログラムにする必要がある。他の禁手としては、駒の利きが盤上に存在しないようにする手、玉を取られる着手、連続王手の千日手、駒の動かし方の間違いがあげられる。

駒の利き

玉を取られる着手としては、①「王手がかかった場合には必ず避けないといけない」ことと、②「ある駒を移動したことで、角・飛車・香車などの飛び駒で王手がかかるようにしないといけない」ことの2種類が禁手として考えられる。

王手がかかった場合の対処は別途関数を作成して対応するが、飛び駒による王手がかからないようにするには、チェスにある「ピン」という概念を導入する。

「ピン」するとは、飛び駒の利きにより、相手の玉と自分の飛び駒の間の相手の駒の動きを制限することである。たとえば、図23では、7八の金が3八の飛車によりピンされているため、金は横にしか移動することができない。(例えば6七や7九に移動してしまうと、玉が飛車で取られてしまう。)

図23

「ピン」という概念を導入するために、局面には、「利き」に関する情報と、玉の位置に関する情報も保持することとする。「利き」に関する情報は、以下のように保持する。

typedef unsinged int Kiki;

利きは、どの方向(Direct[n])から利いているか、「1<<nビット目」を立てて表わすことにする。また、飛び駒の利きは、「1<<(n+16)ビット目」を立てて表わす。

たとえば、図24における「1一」の地点の利きは、①飛車が左から利いているので「(1<<4+16)」+②桂馬が2三から利いているので「(1<<8)」+③歩が1二から利いているので「(1<<6)」となり、2進数で「10000000000010100000」、16進なら、「0x000800C0」となる。

図24

Kiki controlS[16*11];  // 味方の駒の利き
Kiki controlE[16*11];  // 敵の駒の利き

int kingS;
int kingE;

これらも局面クラスのメンバーである。

ピンを用いながら、駒を移動する手を生成するには、たとえば以下のようになる。

// pinが設定されていない、またはピンを同じか逆方向なら移動可能として手のリストに加える
if(pin[i]==0 || pin==dir || pin==-dir) {
  teBuf[teNum++]=Te(from,from+dir,koma,ban[from+dir],0);
}

手のクラスは、移動元、移動先、移動する駒、取った駒、成り・不成りを表せる必要がある。さらに、将来の拡張用として、手の種類と手の価値を加え、手クラスは以下のように定義した。

// 手のクラス
class Te {
public:
  // どこから・どこへはそれぞれ1Byteであらわせます。
  // 詳しくは局面クラスを参照して下さい。
  unsigned char from,to;
  // 動かした駒
  KomaInf koma;
  // 取った駒
  KomaInf capture;
  // 成/不成り
  unsigned char promote;
  // これは、手の生成の際に種別を用いたい時に使います(将来の拡張用)
  unsigned char Kind;
  // その手の仮評価(手の価値)です
  short value;
public:
  inline bool IsNull() {
    return from==0 && to==0;
  }
  // Teの配列を宣言することが多いので、空のコンストラクタを用意します。
  // こうすることで、Teの配列を宣言するたびに無駄な初期化処理が走ることを避けられます。
  inline Te() {};
  // 本当にTeを空のデータで初期化したい時のためのコンストラクタです。
  inline Te(int i) {
    from=to=koma=capture=promote=Kind=0;
    value=0;
  }
  inline Te(int f,int t,KomaInf k,KomaInf c,int p=0,int K=0,int v=0) {
    from=f;
    to=t;
    koma=k;
    capture=c;
    promote=p;
    Kind=K;
    value=v;
  }
  // 手を表示したい時に使います。
  void Print() {
    FPrint(stdout);
  }
  // 同上
  void FPrint(FILE *fp);
  // 手の同一性を比較したいときに使います。KindやValueが違っても同じ手です。
  int operator==(const Te &a) {
    return a.from==from && a.to==to && a.koma==koma && a.promote==promote;
  }
};

局面クラスの全体は、手の生成に必要な関数を含め、以下のように定義した。

// 局面のクラス
class Kyokumen {
public:
  // 桂馬の利きがbanからはみ出すので、はみ出す分を確保しておきます。
  // C++では、構造体の内部の変数の並び順は宣言した順になることを利用しています。
  // 普通はあまり使わない「汚い」テクニックですけど、こういうテクニックもあるということで。
  KomaInf banpadding[16];
  // 2次元配列を使うと遅いので、1次元配列を使います。また、掛け算の際に、*9とかを用いるよりも、
  // 2の階乗を掛け算に使うと掛け算が早くなるので、*16を使います。
  // 駒の位置は、例えば7七なら、7*16+七のようにあらわします。
  // つまり、7七なら16進数で0x77になるわけです。
  KomaInf ban[16*11];

  // 駒の利きを保持します。敵の駒と自分の駒の利きは別々に保持します。
  Kiki controlS[16*11];
  Kiki controlE[16*11];

  // 持ち駒です。王が持ち駒になることはないので、EHIまでで十分です。
  // Hand[SFU]が1なら先手の持ち駒に歩が1枚、Hand[EKI]が3なら敵の持ち駒に金が3枚という要領です。
  int Hand[EHI+1];

  // この局面の手数です。
  int Tesu;

  // 互いの玉の位置です。
  int kingS;
  int kingE;

protected:
  void InitControl();
  int Utifudume(int SorE,int to,int *pin);
  void MoveKing(int SorE,int &teNum,Te *teTop,Kiki kiki);      //玉の動く手の生成
  void MoveTo(int SorE,int &teNum,Te *teTop,int to,int *pin);    //toに動く手の生成
  void PutTo(int SorE,int &teNum,Te *teTop,int to,int *pin);    //toに駒を打つ手の生成
  Kiki CountControlS(int pos);
  Kiki CountControlE(int pos);
  Kiki CountMove(int SorE,int pos,int *pin);
  void AddMoves(int SorE,int &teNum,Te *teTop,int from,int pin,int Rpin=0);
  void AddStraight(int SorE,int &teNum,Te *teTop,int from,int dir,int pin,int Rpin=0);
  void AddMove(int SorE,int &teNum,Te *teTop,int from,int diff,int pin,int Rpin=0);

public:
  inline int search(int pos,int dir) const {
    do {
      pos+=dir;
    }while(ban[pos]==EMPTY);
    return pos;
  }
  Kyokumen() {};
  Kyokumen(int tesu,KomaInf ban[9][9],int Motigoma[]);
  void Print() {
    FPrint(stdout);
  }
  void FPrint(FILE *fp);
  void MakePinInf(int *pin) const;
  int MakeLegalMoves(int SorE,Te *tebuf,int *pin=NULL);
  int AntiCheck(int SorE,Te *tebuf,int *pin,Kiki control);
  void Move(int SorE,const Te &te);
};


// ピン(動かすと王を取られてしまうので動きが制限される駒)の状態を設定する
void Kyokumen::MakePinInf(int *pin) const
{
  int i;
  // ピン情報を設定する
  for (i = 0x11; i <= 0x99; i++) {
    // 0はピンされていない、という意味
    pin[i] = 0;
  }
  if (kingS) {  //自玉が盤面にある時のみ有効
    for (i = 0; i < 8; i++) {
      int p;
      p = search(kingS, Direct[i]); 
      if ((ban[p] != WALL) && !(ban[p] & ENEMY)) { //味方の駒が有る
        if (controlE[p]&1<<(16+i)) {
          pin[p]=Direct[i];
        }
      }
    }
  }
  if (kingE) {  //敵玉が盤面にある時のみ有効
    for (i = 0; i < 8; i++) {
      int p;
      p = search(kingE, -Direct[i]);
      if ((ban[p] != WALL) && (ban[p] & ENEMY)) { //敵の駒が有る
        if (controlS[p]&1<<(16+i)) {
          pin[p]=Direct[i];
        }
      }
    }
  }
}

// 駒の動きとして正しい動きを全て生成する。
int Kyokumen::MakeLegalMoves(int SorE,Te *teBuf,int *pin)
{
  int pbuf[16*11];
  int teNum=0;
  if (pin==NULL) {
    MakePinInf(pbuf);
    pin=pbuf;
  }
  if (SorE==SELF && controlE[kingS]!=0) {
    return AntiCheck(SorE,teBuf,pin,controlE[kingS]);
  }
  if (SorE==ENEMY && controlS[kingE]!=0) {
    return AntiCheck(SorE,teBuf,pin,controlS[kingE]);
  }

  int suji,dan;
  int StartDan,EndDan;
  // 盤上の駒を動かす
  for(suji=0x10;suji<=0x90;suji+=0x10) {
    for(dan=1;dan<=9;dan++) {
      if (ban[suji+dan]&SorE) {
        AddMoves(SorE,teNum,teBuf,suji+dan,pin[suji+dan]);
      }
    }
  }
  // 歩を打つ
  if (Hand[SorE|FU]>0) {
    for(suji=0x10;suji<=0x90;suji+=0x10) {
      // 二歩チェック
      int nifu=0;
      for(dan=1;dan<=9;dan++) {
        if (ban[suji+dan]==SorE|FU) {
          nifu=true;
          break;
        }
      }
      if (nifu) continue;
      //(先手なら2段目より下に、後手なら8段目より上に打つ)
      if (SorE==SELF) {
        StartDan=2;
        EndDan=9;
      } else {
        StartDan=1;
        EndDan=8;
      }
      for(dan=StartDan;dan<=EndDan;dan++) {
        // 打ち歩詰めもチェック
        if (ban[dan+suji]==EMPTY && !Utifudume(SorE,dan+suji,pin)) {
          teBuf[teNum++]=Te(0,suji+dan,SorE|FU,EMPTY);
        }
      }
    }
  }
  // 香を打つ
  if (Hand[SorE|KY]>0) {
    for(suji=0x10;suji<=0x90;suji+=0x10) {
      //(先手なら2段目より下に、後手なら8段目より上に打つ)
      if (SorE==SELF) {
        StartDan=2;
        EndDan=9;
      } else {
        StartDan=1;
        EndDan=8;
      }
      for(dan=StartDan;dan<=EndDan;dan++) {
        if (ban[dan+suji]==EMPTY) {
          teBuf[teNum++]=Te(0,suji+dan,SorE|KY,EMPTY);
        }
      }
    }
  }
  //桂を打つ
  if (Hand[SorE|KE]>0) {
    //(先手なら3段目より下に、後手なら7段目より上に打つ)
    for(suji=0x10;suji<=0x90;suji+=0x10) {
      if (SorE==SELF) {
        StartDan=3;
        EndDan=9;
      } else {
        StartDan=1;
        EndDan=7;
      }
      for(dan=StartDan;dan<=EndDan;dan++) {
        if (ban[dan+suji]==EMPTY) {
          teBuf[teNum++]=Te(0,suji+dan,SorE|KE,EMPTY);
        }
      }
    }
  }
  // 銀~飛車は、どこにでも打てる
  for(int koma=GI;koma<=HI;koma++) {
    if (Hand[SorE|koma]>0) {
      for(suji=0x10;suji<=0x90;suji+=0x10) {
        for(dan=1;dan<=9;dan++) {
          if (ban[dan+suji]==EMPTY) {
            teBuf[teNum++]=Te(0,suji+dan,SorE|koma,EMPTY);
          }
        }
      }
    }
  }
  
  return teNum;
}

// 盤面のfromにある駒を動かす手を生成する。
void Kyokumen::AddMoves(int SorE,int &teNum,Te *teTop,int from,int pin,int Rpin)
{
  switch(ban[from]) {
  case SFU:
    AddMove(SorE,teNum,teTop,from,-1,pin,Rpin);
    break;
  case EFU:
    AddMove(SorE,teNum,teTop,from,+1,pin,Rpin);
    break;
  case SKY:
    AddStraight(SorE,teNum,teTop,from,-1,pin,Rpin);
    break;
  case EKY:
    AddStraight(SorE,teNum,teTop,from,+1,pin,Rpin);
    break;
  case SKE:
    AddMove(SorE,teNum,teTop,from,+14,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-18,pin,Rpin);
    break;
  case EKE:
    AddMove(SorE,teNum,teTop,from,-14,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,+18,pin,Rpin);
    break;
  case SGI:
    AddMove(SorE,teNum,teTop,from, -1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 15,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-15,pin,Rpin);
    break;
  case EGI:
    AddMove(SorE,teNum,teTop,from, +1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-15,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 15,pin,Rpin);
    break;
  case SKI:case STO:case SNY:case SNK:case SNG:
    AddMove(SorE,teNum,teTop,from, -1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 15,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, +1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-16,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 16,pin,Rpin);
    break;
  case EKI:case ETO:case ENY:case ENK:case ENG:
    AddMove(SorE,teNum,teTop,from, +1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-15,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, -1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-16,pin,Rpin);
    AddMove(SorE,teNum,teTop,from, 16,pin,Rpin);
    break;
  case SRY:case ERY:
    AddMove(SorE,teNum,teTop,from, 17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-15,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-17,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,+15,pin,Rpin);
  case SHI:case EHI:
    AddStraight(SorE,teNum,teTop,from,+1,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,-1,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,-16,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,+16,pin,Rpin);
    break;
  case SUM:case EUM:
    AddMove(SorE,teNum,teTop,from,+1,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,+16,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-16,pin,Rpin);
    AddMove(SorE,teNum,teTop,from,-1,pin,Rpin);
  case SKA:case EKA:
    AddStraight(SorE,teNum,teTop,from,+17,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,-17,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,+15,pin,Rpin);
    AddStraight(SorE,teNum,teTop,from,-15,pin,Rpin);
    break;
  case SOU:case EOU:
    MoveKing(SorE,teNum,teTop,0);  // 王手がかかっている時には、AntiCheckの方が呼ばれるから、Kikiは0です。
  }
}

// ある場所の利き情報を作成して返す。普段は使わない関数(差分計算しているから)だが、
// 打ち歩詰めのチェックなど、駒を仮に置いてみて何かするようなときに使用する。
Kiki Kyokumen::CountControlS(int pos)
{
  Kiki ret=0;
  int i,b,bj;
  b=1;
  bj=1<<16;
  for(i=0;i<12;i++,b<<1,bj<<1) {
    if (CanMove[i][ban[pos-Direct[i]]] && (ban[pos-Direct[i]]&SELF)) {
      ret|=b;
    } else if (CanJump[i][ban[search(pos,-Direct[i])]] && (ban[search(pos,-Direct[i])]&SELF)) {
      ret|=bj;
    }
  }
  return ret;
}

// ある場所の利き情報を作成して返す。普段は使わない関数(差分計算しているから)だが、
// 打ち歩詰めのチェックなど、駒を仮に置いてみて何かするようなときに使用する。
Kiki Kyokumen::CountControlE(int pos)
{
  Kiki ret=0;
  int i,b,bj;
  b=1;
  bj=1<<16;
  for(i=0;i<12;i++,b<<1,bj<<1) {
    if (CanMove[i][ban[pos-Direct[i]]] && (ban[pos-Direct[i]]&ENEMY)) {
      ret|=b;
    } else if (CanJump[i][ban[search(pos,-Direct[i])]] && (ban[search(pos,-Direct[i])]&ENEMY)) {
      ret|=bj;
    }
  }
  return ret;
}

// ある場所に移動できる駒を全部集めて、Kiki情報にして返す。
// このとき、pinされている駒はpinの方向にしか動けない。
Kiki Kyokumen::CountMove(int SorE,int pos,int *pin)
{
  Kiki ret=0;
  int i,b,bj;
  b=1;
  bj=1<<16;
  for(i=0;i<12;i++,b<<1,bj<<1) {
    if (CanMove[i][ban[pos-Direct[i]]] && (ban[pos-Direct[i]]&ENEMY) && 
      (pin[pos-Direct[i]]==0||pin[pos-Direct[i]]==Direct[i]||pin[pos-Direct[i]]==-Direct[i])) {
      ret|=b;
    } else if (CanJump[i][ban[search(pos,-Direct[i])]] && (ban[search(pos,-Direct[i])]&ENEMY)&& (pin[search(pos,-Direct[i])]==0||pin[search(pos,-Direct[i])]==Direct[i]||pin[search(pos,-Direct[i])]==-Direct[i])) {
      ret|=bj;
    }
  }
  return ret;
}

// 打ち歩詰めの判定
int Kyokumen::Utifudume(int SorE,int to,int *pin)
{
  if (SorE==SELF) {
    // まず、玉の頭に歩を打つ手じゃなければ打ち歩詰めの心配はない。
    if (kingE+1!=to) {
      return 0;
    }
  } else {
    // まず、玉の頭に歩を打つ手じゃなければ打ち歩詰めの心配はない。
    if (kingS-1!=to) {
      return 0;
    }
  }
  //実際に歩を打って確かめてみる。
  ban[to]=FU|SorE;
  if (SorE==SELF) {
    // 自分の利きがあったら相手は玉で取れない & 取る動きを列挙してみたら玉で取る手しかない
    if (controlS[to] && (CountMove(ENEMY,to,pin)==1<<2)) {
      // 玉に逃げ道があるかどうかをチェック
      for(int i=0;i<8;i++) {
        KomaInf koma=ban[kingE+Direct[i]];
        if (!(koma & ENEMY) && !CountControlS(kingE+Direct[i])) {
          // 逃げ道があったので、盤面を元の状態に戻して、
          ban[to]=EMPTY;
          // 打ち歩詰めではなかった。
          return 0;
        }
      }
      // 玉の逃げ道もないのなら、打ち歩詰め。盤面の状態は元に戻す。
      ban[to]=EMPTY;
      return 1;
    }
    // 玉以外で取る手があるので打ち歩詰めではない。
    ban[to]=EMPTY;
    return 0;
  } else {
    // 自分の利きがあったら相手は玉で取れない & 取る動きを列挙してみたら玉で取る手しかない
    if (controlE[to] && (CountMove(SELF,to,pin)==1<<6)) {
      // 玉に逃げ道があるかどうかをチェック
      for(int i=0;i<8;i++) {
        KomaInf koma=ban[kingS+Direct[i]];
        if (!(koma & SELF) && !CountControlE(kingS+Direct[i])) {
          // 逃げ道があったので、盤面を元の状態に戻して、
          ban[to]=EMPTY;
          // 打ち歩詰めではなかった。
          return 0;
        }
      }
      // 玉の逃げ道もないのなら、打ち歩詰め。盤面の状態は元に戻す。
      ban[to]=EMPTY;
      return 1;
    }
    // 玉以外で取る手があるので打ち歩詰めではない。
    ban[to]=EMPTY;
    return 0;
  }
}

// ある場所(to)に駒を打つ手の生成
void Kyokumen::PutTo(int SorE,int &teNum,Te *teTop,int to,int *pin)
{
  int dan=to &0x0f;
  if (SorE==ENEMY) {
    dan=10-dan;
  }
  if (Hand[SorE|FU]>0 && dan>1) {
    // 歩を打つ手を生成
    // 二歩チェック
    int suji=to & 0xf0;
    int nifu=0;
    for(int d=1;d<=9;d++) {
      if (ban[suji+d]==(SorE|FU)) {
        nifu=1;
        break;
      }
    }
    // 打ち歩詰めもチェック
    if (!nifu && !Utifudume(SorE,to,pin)) {
      teTop[teNum++]=Te(0,to,SorE|FU,EMPTY);
    }
  }
  if (Hand[SorE|KY]>0 && dan>1) {
    // 香を打つ手を生成
    teTop[teNum++]=Te(0,to,SorE|KY,EMPTY);
  }
  if (Hand[SorE|KE]>0 && dan>2) {
    teTop[teNum++]=Te(0,to,SorE|KE,EMPTY);
  }
  for(int koma=GI;koma<=HI;koma++) {
    if (Hand[SorE|koma]>0) {
      teTop[teNum++]=Te(0,to,SorE|koma,EMPTY);
    }
  }
}

// 王手を受ける手の生成
int Kyokumen::AntiCheck(int SorE,Te *teBuf,int *pin,Kiki kiki)
{
  int king;
  int teNum=0;
  if ((kiki & (kiki-1))!=0) {
    //両王手は玉を動かすしかない
    MoveKing(SorE,teNum, teBuf, kiki);
  } else {
    if (SorE==SELF) {
      king=kingS;
    } else {
      king=kingE;
    }
    unsigned int id;
    int check;
    for (id = 0; id <= 31; id++) {
      if (kiki == (1u << id)) break;
    }
    if (id < 16) {
      check = king - Direct[id];
    } else {
      check = search(king,-Direct[id-16]);
    }
    //王手駒を取る
    MoveTo(SorE,teNum, teBuf, check, pin);
    
    //玉を動かす
    MoveKing(SorE,teNum, teBuf, kiki);

    if (id >= 16) {
      //合駒をする手を生成する
      int i;
      for (i = king - Direct[id-16]; ban[i] == EMPTY; i -= Direct[id-16]) {
        MoveTo(SorE,teNum, teBuf, i, pin); //移動合
      }
      for (i = king - Direct[id-16]; ban[i] == EMPTY; i -= Direct[id-16]) {
        PutTo(SorE,teNum, teBuf, i, pin);  //駒を打つ合
      t}
    } 
  }
  return teNum;
}

// 玉を動かす手の生成
// 普通の駒と違い、相手の利きのあるところには動けないので、そのための特殊な処理をしています。
// そのために、最後にkikiという変数を入れています。
void Kyokumen::MoveKing(int SorE,int &teNum,Te *teTop,Kiki kiki)
{
  int i;
  int id = -1;  //隣接王手駒の位置のid
  // 両王手でないなら王手駒の位置を探す
  for (i = 0; i < 8; i++) {
    if (kiki & (1 << i)) {
      id = i;
      break;
    }
  }
  if (id >= 0) {
    // 隣接の王手 最初に取る手を生成するのだ
    if (SorE==SELF) {
      KomaInf koma=ban[kingS-Direct[id]];
      if (( koma==EMPTY || (koma & ENEMY))
        && !CountControlE(kingS - Direct[id]) //敵の駒が効いていない
        && !(kiki & (1 << (23-id))))  //敵の飛駒で貫かれていない
      AddMove(SorE,teNum, teTop, kingS, -Direct[id], 0);
    } else {
      KomaInf koma=ban[kingE-Direct[id]];
      if (( koma==EMPTY || (koma & SELF))
        && !CountControlS(kingE - Direct[id]) //敵の駒が効いていない
        && !(kiki & (1 << (23-id))))  //敵の飛駒で貫かれていない
      AddMove(SorE,teNum, teTop, kingE, -Direct[id], 0);
    }
  }
  for (i = 0; i < 8; i++) {
    if (i == id) continue;
    if (SorE==SELF) {
      KomaInf koma=ban[kingS-Direct[i]];
      if (( koma==EMPTY || (koma & ENEMY))
        && !CountControlE(kingS - Direct[i]) //敵の駒が効いていない
        && !(kiki & (1 << (23-i))))  //敵の飛駒で貫かれていない
      AddMove(SorE,teNum, teTop, kingS, -Direct[i], 0);
    } else {
      KomaInf koma=ban[kingE-Direct[i]];
      if (( koma==EMPTY || (koma & SELF))
        && !CountControlS(kingE - Direct[i]) //敵の駒が効いていない
        && !(kiki & (1 << (23-i))))  //敵の飛駒で貫かれていない
      AddMove(SorE,teNum, teTop, kingE, -Direct[i], 0);
    }
  }
}

// 手の生成:成り・不成りも意識して、駒の動く手を生成する。
void Kyokumen::AddMove(int SorE,int &teNum,Te *teTop,int from,int diff,int pin,int Rpin)
{
  if (Rpin==diff||Rpin==-diff) {
    return;
  }
  int to=from+diff;
  int dan=to&0x0f;
  int fromDan=from&0x0f;

  if ((pin==0 || pin==diff || pin==-diff) && !(ban[to]&SorE)) {
    if (ban[from]==SKE && dan<=2) {
      // 必ず成る
      teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
    } else if ((ban[from]==SFU || ban[from]==SKY) && dan<=1) {
      // 必ず成る
      teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
    } else if (ban[from]==EKE && dan>=8) {
      // 必ず成る
      teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
    } else if ((ban[from]==EFU || ban[from]==EKY) && dan>=9) {
      // 必ず成る
      teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
    } else {
      if (SorE==SELF && (fromDan<=3 || dan <=3) && CanPromote[ban[from]]) {
        teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
      } else if (SorE==ENEMY && (fromDan>=7 || dan>=7) && CanPromote[ban[from]]){
        teTop[teNum++]=Te(from,to,ban[from],ban[to],1);
      }
      // 成らない手も生成する。
      teTop[teNum++]=Te(from,to,ban[from],ban[to],0);
    }
  }
}

// 飛車角香車がまっすぐに進む手の生成
void Kyokumen::AddStraight(int SorE,int &teNum,Te *teTop,int from,int dir,int pin,int Rpin)
{
  if (dir==Rpin || dir==-Rpin) {
    return;
  }
  int i;
  if (pin==0 || pin==dir || pin==-dir) {
    // 空白の間、動く手を生成する
    for(i=dir;ban[from+i]==EMPTY;i+=dir) {
      AddMove(SorE,teNum,teTop,from,i,0);
    }
    // 味方の駒でないなら、そこへ動く
    if (!(ban[from+i] & SorE)) {
      AddMove(SorE,teNum,teTop,from,i,0);
    }
  }
}

//toに動く手の生成
void Kyokumen::MoveTo(int SorE,int &teNum,Te *teTop,int to,int* pin)
{
  int p;
  KomaInf koma;

  for(int i=0;i<12;i++) {
    if ((koma=ban[to-Direct[i]]) == EMPTY) {
      p=search(to,-Direct[i]);
      if ((ban[p]&SorE) && CanJump[i][ban[p]]) {
        AddMove(SorE,teNum,teTop,p,to-p,pin[p]);
      }
    } else {
      if ((koma&~SorE)!=OU && (koma&SorE) && (CanMove[i][koma]||CanJump[i][koma])) {
        AddMove(SorE,teNum,teTop,to-Direct[i],Direct[i],pin[to-Direct[i]]);
      }
    }
  }
}

将棋プログラムで、一番つらいのは実際にはこの辺りをプログラムしている時なので、ここまで読み終えたあなたは、プログラムの前半の最大の山場を越えたと思ってもらって良い。なお、この章では持ち越しの課題とした、千日手および連続王手の千日手については、第5章にて対応する。

さて、このままでは、指し手の生成ができるだけなので、いよいよ次の章では実際に今までに作成した関数群を使って、対戦可能なプログラムに仕上げてみたい。


前ページ
次ページ
索引ページへ