駒をなる時は、最後に - をつけます。例えば、5五の飛車が5三に成る時は、5553- とします。
持ち駒を打つときは、1七歩打ちの場合は、 0117 とします。最初の01が歩を示し、次の17が打つ場所を示します。
その他の操作方法は、? で表示されます。
4人将棋ダブルスソフト
#4人将棋 # 駒の番号 1歩 2銀 3金 4飛 5と 6成銀(全) 7× 8龍 9玉 # 手番 1(玉が5九の人),2(玉が9五の人),3,4 # その枡の周囲番号 # 6 4 1 # 7◎ 2 # 8 5 3 # #大域変数 # @MAN[1..4] 各対局者をどちらが受け持つか 1:人 0:コンピュータ # $DEPTH コンピュータの思考深さ # $TEBAN 次の手番(基本的に人のメインのみで使う) # #内部変数 # @KATI[1..9] #盤面変数 # @BAN[11..99] 駒番号 盤の右上から下に11,12,,,, # @MKI[0..110] 駒の向き 1,2,3,4:対局者 0:駒なし -1:盤外 # @MTI[1..4] 持ち駒 各駒の枚数を4桁で表す (歩銀金飛) # @KKI[11..99] 枡に対して周囲の誰の駒が利いているか # 8桁 (右上,右,右下,上,下,左上,左,左下) # 例:初期状態の2八にはA(左下)とD(右上)の駒が利いている # $KKI[28]="4 1" # @OU[1..4] 各人の玉の位置 2桁 例:5九にいるなら"59" # # @SASITE[1..99] 指し手 "13928"等 1番が3九の銀を2八に動かした #王手かのチェックが不十分(飛車の王手が未対応) srand time; &init; #現在の局面 @KATI[1..9] = (1, 5, 5, 8, 6, 5, 0, 10, 969); #駒の価値 @MAN[1..4] = (1,0,0,0); #人が指すかPCが指すか $DEPTH = 2; #コンピュータの読み深さ $msg = "あなたの番です"; @SASITE = (); for(;;){ $TEBAN = &tugi($TEBAN); if(@MAN[$TEBAN]){ #人が指す $msg .= "王手がかかっています" if(&is_oute($TEBAN)); $te = &man_input($msg); if($te<0){ #待った if($#SASITE+$te>=-1){ @SASITE = @SASITE[0..$#SASITE+$te]; &init; for ($i=0; $i<=$#SASITE ; $i++){ &move($SASITE[$i]); } $TEBAN = ($#SASITE<0)?0:substr($SASITE[$#SASITE], 0, 1); $msg = "待ったしました"; }else{ $msg = "待ったできません"; } next; }elsif($te == 1){ last; }else{ &move($te); $msg = &jp($te)." "; } }else{ #コンピュータが指す print "##### ".substr("ABCD",$TEBAN-1,1).":思考中です #### "; ($te, $v) = &solve($TEBAN); &move($te); print &jp($te), " [$te]\n"; $msg .= &jp($te)." "; if($te == 1){&ban_disp; last}; } push(@SASITE, $te); } #盤の初期状態作成 sub init{ @BAN[11..19] = (0, 0, 2, 3, 9, 3, 2, 0, 0); @BAN[21..29] = (0, 0, 0, 1, 4, 1, 0, 0, 0); @BAN[31..39] = (2, 0, 0, 0, 1, 0, 0, 0, 2); @BAN[41..49] = (3, 1, 0, 0, 0, 0, 0, 1, 3); @BAN[51..59] = (9, 4, 1, 0, 0, 0, 1, 4, 9); @BAN[61..69] = (3, 1, 0, 0, 0, 0, 0, 1, 3); @BAN[71..79] = (2, 0, 0, 0, 1, 0, 0, 0, 2); @BAN[81..89] = (0, 0, 0, 1, 4, 1, 0, 0, 0); @BAN[91..99] = (0, 0, 2, 3, 9, 3, 2, 0, 0); @MKI[ 0.. 9] = (-1,-1,-1,-1,-1,-1,-1,-1,-1,-1); @MKI[10..19] = (-1, 0, 0, 4, 4, 4, 4, 4, 0, 0); @MKI[20..29] = (-1, 0, 0, 0, 4, 4, 4, 0, 0, 0); @MKI[30..39] = (-1, 3, 0, 0, 0, 4, 0, 0, 0, 1); @MKI[40..49] = (-1, 3, 3, 0, 0, 0, 0, 0, 1, 1); @MKI[50..59] = (-1, 3, 3, 3, 0, 0, 0, 1, 1, 1); @MKI[60..69] = (-1, 3, 3, 0, 0, 0, 0, 0, 1, 1); @MKI[70..79] = (-1, 3, 0, 0, 0, 2, 0, 0, 0, 1); @MKI[80..89] = (-1, 0, 0, 0, 2, 2, 2, 0, 0, 0); @MKI[90..99] = (-1, 0, 0, 2, 2, 2, 2, 2, 0, 0); @MKI[100..109]=(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1); @MTI[1..4] = ("0000","0000","0000","0000"); @OU[1..4] = (59, 95, 51, 15); &make_kki; } #@BANと@MKIから@KKIを作成する sub make_kki{ my($teban, $i); for $i (0..109){ $KKI[$i] = " "; } for $i (11..99){ add_kki($i) if($MKI[$i]>0); #その枡の駒の利きを追加 } } #指定した枡にある駒の利き(飛車の飛び以外)を追加 #指定した枡には駒があること sub add_kki{ my($xy) = @_; my($teban, $i); $teban = $MKI[$xy]; for $i (&ugoki($xy)){ substr($KKI[$i], 9-&shui($i-$xy)-1, 1) = $teban; } } #指定した枡にある駒の利きを削除 #指定した枡には駒があること sub del_kki{ my($xy) = @_; my($i); for $i (&ugoki($xy)){ substr($KKI[$i], 9-&shui($i-$xy)-1, 1) = " "; } } #飛車の隣枡以外で利いている枡を求める #引数:(枡位置,方向) 方向(1:上、2:下、3:右、4:下) #戻り値:飛車が利いている枡の座標のリスト sub hisha{ my($xy,$dir) = @_; my($teban) = $MKI[$xy]; my($step) = (0,-1, 1,-10, 10)[$dir]; my(@m) = (); if($step==0){ return (&hisha($xy,1),&hisha($xy,2),&hisha($xy,3),&hisha($xy,4)); } if(!$MKI[$xy+$step]){ #隣に駒があれば終わり for($xy += 2*$step;; $xy += $step){ #隣の隣から検索開始 last if($MKI[$xy]<0); #盤外なら終わり @m = (@m, $xy); last if($MKI[$xy]); #駒があれば終わり } } return @m; } #指定した駒が利いている枡位置を求める(盤外も求めてしまう) #飛車、龍では隣以外の枡は返さない #引数:駒位置 #戻り値:指定した駒の利いている枡位置のリスト sub ugoki{ my ($xy) = @_; my ($i); my (@m) = &ugoki0($MKI[$xy], $BAN[$xy]); for ($i=0; $i<=$#m; $i++){ $m[$i] += $xy;} return @m; } sub ugoki0{ my($teban, $koma) = @_; if($koma == 1){ #歩 return -1 if($teban == 1); return -10 if($teban == 2); return 1 if($teban == 3); return 10 if($teban == 4); }elsif($koma == 2){ #銀 return (-11,-9, -1,9,11) if($teban==1); return (-11,-9,-10,9,11) if($teban==2); return (-11,-9, 1,9,11) if($teban==3); return (-11,-9, 10,9,11) if($teban==4); }elsif($koma =~ /^[356]$/){ #金と全 return (-11,-10,-1, 1, 9,10) if($teban==1); return (-11,-10,-9,-1, 1,10) if($teban==2); return (-10, -9,-1, 1,10,11) if($teban==3); return (-10, -1, 1, 9,10,11) if($teban==4); }elsif($koma == 4){ #飛 return (-10,-1,1,10); }elsif($koma =~ /^[89]$/){ #龍玉 return (-11,-10,-9,-1,1,9,10,11); }else{ print "****** おかしな駒 $koma *******"; } } #枡の相対位置から周囲番号(1..8)を求める #引数:枡の相対位置 #戻り値:周囲番号 sub shui{ my($xy)=@_; return $xy+12 if($xy <=-9); return 4 if($xy == -1); return 5 if($xy == 1); return $xy-3 if($xy >=9); } #枡の周囲の番号(1..8)から相対位置を求める #引数:枡の周囲番号 #戻り値:枡の相対番号 sub soutai{ my($shui) = @_; my(@a) = (-11, -10, -9, -1, 1, 9,10,11); return $a[$shui-1]; } #盤面の駒の移動 #引数:指し手 #戻り値: 0:正常 1:玉を取った 2:それ以外 sub move{ my ($a) = @_; my ($teban, $moto, $ato, $naru) = ( $a =~ /^(\d)(\d\d)(\d\d)(-?)/ ); my ($ban, $koma ); return 2 if($teban == 0); if($koma = $BAN[$ato]){ #移動先に駒が有れば持駒にする return 1 if($koma==9); &del_kki($ato) if($BAN[$ato]); #とられる駒の利きをなくす $koma -= 4 if(4 < $koma); substr($MTI[$teban], $koma-1, 1)++; } if($moto <= 9){ #駒を打つ場合 $BAN[$ato] = $moto+0; $MKI[$ato] = $teban+0; &add_kki($ato); substr($MTI[$teban], $moto-1, 1)--; }else{ #駒を移動する場合 &del_kki($moto); #元の位置の利きをなくす $BAN[$ato] = $BAN[$moto]; $BAN[$ato] += 4 if($naru); $MKI[$ato] = $MKI[$moto]; &add_kki($ato); $BAN[$moto] = 0; $MKI[$moto] = 0; $OU[$teban] = $ato if($BAN[$ato] == 9); #玉の位置 } return 0; } #指し手チェックの結果 sub NGmsg{ my($a) = @_; my(@b) = qw(持ち駒なし 自分の駒でない 変な動き 成れない 自分の駒を取る 2歩 . . その他); return $b[$a-1]; } #指し手が正しいかのチェック # 引数である指し手そのもののフォーマットは正しいものとする。 #引数:指し手のリスト #戻り値: 0:正常 # 1:持ち駒にない 2:自分の駒でない 3:変な動き 4:成れない 5:自分の駒を取る # 6:2歩 9:それ以外 sub is_NG{ my ($teban, $moto, $ato, $naru) = @_; my ($i,$j,$koma); if($moto!~/^0?[1234]$|^[1-9][1-9]$/ || $ato!~/^[1-9][1-9]$/){ #範囲チェック return 9; }elsif($moto <= 4){ #駒を打つ場合 return 1 if(substr($MTI[$teban], $moto-1, 1)==0); #持駒にあるか return 3 if($MKI[$ato]); #打つ先に駒がある return 4 if($naru); #成って打とうとする return 6 if(&nifu($teban, $ato)); #2歩 return 3 if($moto == 1 && &saigo($teban, $ato)); #行き所のない場所に歩を打つ }else{ return 2 if($teban != $MKI[$moto]); #元の駒が自分のかの確認 return 5 if($MKI[$ato] == $teban); #自分の駒を取る $koma = $BAN[$moto]; is_NG3:{ for $i (&ugoki($moto)){last is_NG3 if($ato == $i)} #駒の動き if($koma==4 || $koma == 8){ #飛車の飛び動きの確認 for $i (&hisha($moto, 0)){last is_NG3 if($i==$ato);}; } return 3; #動きが変 } return 3 if($koma==1 && &saigo($teban,$ato) && !$naru); #歩が進めない return 4 if($naru && !nareru($moto,$ato)); #駒が成れる位置にあるか } return 0; } #駒が成れるかのチェック #戻り値:1:成れる 0:成れない sub nareru{ my($moto, $ato)=@_; my($teban)= $MKI[$moto]; my($koma) = $BAN[$moto]; return 0 if($koma =~ /^[356789]$/); return 0 if($teban==1 && $ato !~ /[123]$/ && $moto !~ /[123]$/); return 0 if($teban==2 && $ato !~ /^[123]/ && $moto !~ /^[123]/); return 0 if($teban==3 && $ato !~ /[789]$/ && $moto !~ /[789]$/); return 0 if($teban==4 && $ato !~ /^[789]/ && $moto !~ /^[789]/); return 1; } #王手が掛かっているかのチェック #引数:手番 #戻り値:1:王手がかかっている 0:王手が掛かっていない sub is_oute{ my ($teban) = @_; my($c); $c = $KKI[$OU[$teban]]; #隣の枡のチェック $c =~ s/$teban//g; return 1 if($c>0); for $i (&hisha($OU[$teban], 0)){ #遠距離の飛車のチェック return 1 if($BAN[$i]=~/[48]/ && $MKI[$i]!=$teban); }; return 0; } #次の手番を返す sub tugi{ my ($teban) = @_; for (1..3){ $teban = ($teban % 4)+1; return $teban if(&is_oute($teban)); } return ($teban+1) % 4 + 1; } #人間の入力 #引数:表示メッセージ #戻り値:指し手 @##%%(成らず) or @##%%-(成) or 1(投了) or 9(待った) # 指し手の意味 @:対局者 ##:元の位置 %%:移動後の位置 sub man_input{ my ($msg) = @_; my $a,$te; print "\n"; for(;;){ if($msg){ &ban_disp; print "$msg:形勢=".&hyouka."\n"; print $#SASITE+2 , "手目です。"; } printf(" %sの手番です 入力 or ?=>", substr("ABCD", $TEBAN-1, 1)); chop($te=<>); if($te =~ /\?/){ #ヘルプ print " ##%% (##元の位置 %%移動後の位置 成る時は最後に-をつける\n"; print " 打つ時は元の位置の代わりに、01歩 02銀 03金 04飛とする\n"; print " 他の入力 1:投了, -#:待った, .#:思考レベルの変更(現在:$DEPTH)\n"; print " /#:手番を#にする\n"; next; } if($te =~ /KKI(..)/){print "KKI=$KKI[$1]:\n"; $te=="";} if($te =~ /\./){ #思考レベルの変更 ($te , $a) = split(/\./, $te); $DEPTH = $a if($a > 0); } if($te =~ /\/([1-4])/){ #手番変更 $TEBAN = $1; $te =~ s/$1//; } return($te) if($te == 1 || $te < 0); #投了・待った if($te =~ /(.*)\*$/){ #指し手チェックを行わない return $TEBAN.$1; }elsif($te =~ /^(\d\d)(\d\d)(-?)$/){ #指し手のチェック $a = &is_NG($TEBAN, $1, $2, $3); return $TEBAN.$te if(!$a); $msg = "その手はさせません(".substr("ABCD",$TEBAN-1,1).&NGmsg($a).")"; next; } $msg = "入力が違っています"; $msg = "" if(!$te); } } #盤の表示 sub ban_disp{ my (@mes1,@mes2,$i); $mes1[4] = "B:"; $mes1[5] = &motikoma(2); $mes2[4] = "D:"; $mes2[5] = &motikoma(4); printf("%20s C:%s\n", "", &motikoma(3)); printf("%20s 9 8 7 6 5 4 3 2 1\n"); printf("%20s+----------------------------\n"); for($i=1; $i<=9; $i++){ printf("%-20s%s %s\n", $mes1[$i], &ban_1($i), $mes2[$i]); } printf "%20s+----------------------------\n"; printf "%20s A:%s\n", "", &motikoma(1); } #表示持ち駒 sub motikoma{ my ($teban) = @_; my ($koma) = $MTI[$teban]; $koma =~ s/(.)(.)(.)(.)/飛\4 金\3 銀\2 歩\1/; return $koma; } #盤の1行を返す # ban_disp で使用 #引数:盤の縦座標 sub ban_1{ my ($y) = @_; my ($a, $b, $c,$i); $c = "|"; for($i=9; $i>=1; $i--){ $a = $i*10 + $y; $b = $MKI[$a]; $c .= " ".&koma($BAN[$a]) if($b == 1); $c .= &koma($BAN[$a]).">" if($b == 2); $c .= "v".&koma($BAN[$a]) if($b == 3); $c .= "<".&koma($BAN[$a]) if($b == 4); $c .= " ・" if($b == 0); } return $c."|".substr("一二三四五六七八九", $y*2-2, 2); } #駒番号から駒名を返す sub koma{ my($n) = @_; return substr("・歩銀金飛と全金龍玉", $n*2, 2); } #与えられた配列の順番をでたらめにする sub rnd_array{ my(@a) = @_; my($b); for($i=0; $i<=$#a; $i++){ $b = int(rand($#a+1)); ($a[i],$a[$b]) = ($a[$b], $a[i]); } return @a; } #指し手の日本語 sub jp{ my($te) = @_; return "負けました" if($te==1); my($koma); my($teban,$moto,$ato,$naru) = ($te =~ /(.)(..)(..)(.?)/); $teban = ((A,B,C,D)[$teban-1]); $koma = &koma($naru?$BAN[$ato]-4:$BAN[$ato]); $naru = "成" if($naru); $moto = "打" if($moto<10); return "$teban:$ato$koma$naru($moto)"; } #候補手を出す #引数:手番 #戻り値:(候補1、候補2、・・・) sub kouho{ my ($teban) = @_; my (@k, @b, $xy, $xy2); for($xy=11; $xy<=99; $xy++){ #空いている枡 @b = (@b, $xy) if(!$MKI[$xy]); } for $xy (1,2,3,4){ #駒打ち if($a = substr($MTI[$teban],$xy-1,1)){ for $xy2 (@b){ next if($xy == 1 && (&nifu($teban, $xy2)||&saigo($teban,$xy2))); @k = (@k, $teban."0$xy".$xy2); } } } for($xy=11;$xy<=99;$xy++){ #普通の場合の盤上の駒を動かす next if($MKI[$xy] != $teban); @b = &ugoki($xy); @b = (@b, &hisha($xy, 0)) if($BAN[$xy] =~ /[48]/); for $xy2 (@b){ next if($MKI[$xy2] == $teban || $MKI[$xy2] < 0); @k = (@k, $teban.$xy.$xy2) if($BAN[$xy]!=1 || !&saigo($teban,$xy)); @k = (@k, $teban.$xy.$xy2."-") if(nareru($xy,$xy2)); } } return @k; } #指定した枡に利いている自分の駒の座標を求める #戻り値:座標のリスト sub kiki0{ my($xy, $teban) = @_; my($b, $i); my(@k)=(); $b = $KKI[$xy]; for($i=1; $i<=8; $i++){ @k = (@k, &soutai($i)+$xy) if(substr($b, $i-1, 1)==$teban); } return @k; } #指定した枡に利いている他人の駒の座標を求める #戻り値:座標のリスト sub kiki1{ my($xy, $teban) = @_; my($b, $i); my(@k)=(); $b = $KKI[$xy]; $b =~ s/$teban/ /g; for($i=1; $i<=8; $i++){ @k = (@k, &soutai($i)+$xy) if(substr($b, $i-1, 1)>0); } return @k; } #2歩のチェック sub nifu{ my($teban, $xy)=@_; my($i); if($teban =~ /[13]/){ $xy = int($xy/10)*10; for($i=1; $i<=9; $i++){ return 1 if($BAN[$xy+$i] == 1 && $MKI[$xy+$i]==$teban); } }else{ $xy = $xy - int($xy/10)*10; for($i=10; $i<=90; $i+=10){ return 1 if($BAN[$xy+$i] == 1 && $MKI[$xy+$i]==$teban); } } } #歩が進めないことのチェック sub saigo{ my($teban, $xy)=@_; return 1 if($teban == 1 && $xy =~ /1$/); return 1 if($teban == 2 && $xy =~ /^1/); return 1 if($teban == 3 && $xy =~ /9$/); return 1 if($teban == 4 && $xy =~ /^9/); } #考える #引数:(手番、読む深さ) #戻り値:(次の手,その評価値) sub solve0{ my ($teban, $fukasa) = @_; my ($i, $te, @k); my ($teban2, %new_v, $best_v, $best_te); my ($te2); @k = &kouho($teban); #候補手を探す # @k = @k[0..1]; for($i=$#k; $i>=0;$i--){ $te = $k[$i]; push(@BAN_STACK, join(":", @BAN)); push(@MKI_STACK, join(":", @MKI)); push(@MTI_STACK, join(":", @MTI)); push(@KKI_STACK, join(":", @KKI)); push(@OU_STACK , join(":", @OU)); &move($te); #次の局面を作成 if(&is_oute($teban)){ #王手が回避できない手は駄目 splice(@k, $i, 1); }elsif(&is_oute((($teban+1)%4)+1)){ #相棒に王手するのは駄目 splice(@k, $i, 1); }else{ if($fukasa <= 1){ #次の局面の評価を行う $new_v{$te} = &hyouka; }else{ #n手先の局面の評価を行う $teban2 = &tugi($teban); ($te2, $new_v{$te}) = &solve0($teban2, $fukasa-1); } } @BAN = split(/:/, pop(@BAN_STACK)); @MKI = split(/:/, pop(@MKI_STACK)); @KKI = split(/:/, pop(@KKI_STACK)); @MTI = split(/:/, pop(@MTI_STACK)); @OU = split(/:/, pop(@OU_STACK)); } if($teban =~ /[13]/){ $best_v = -9999; $best_te = "1"; foreach $te (@k){ if($new_v{$te} > $best_v){ $best_v = $new_v{$te}; $best_te=$te; } } }else{ $best_v = 9999; $best_te = "1"; foreach $te (@k){ if($new_v{$te} < $best_v){ $best_v = $new_v{$te}; $best_te=$te; } } } #print " #$best_te:$best_v# "; return ($best_te, $best_v); } #局面の評価 #手番1・3組みが優勢なほど大きい値を返す #引数:なし #戻り値:評価値\ sub hyouka{ my(@ko); #駒得のチェック for $i (11..99){ $ko[$MKI[$i]] += $KATI[@BAN[$i]]; } for $i (1..4){ for $j (1..4){ $ko[$i] += $KATI[$j] * substr($MTI[$i], $j-1, 1); }} return $ko[1]-$ko[2]+$ko[3]-$ko[4]; } #考える #引数:(手番) #戻り値:(指し手、その評価値) sub solve{ my ($teban) = @_; ($te, $v) = &solve0($teban, $DEPTH); return ($te, $v); }