装備品
目: ひのきの目
機械学習するとき データを読み込みたくなる。
例えば 将棋なら棋譜を読み込みたくなる。
将棋の棋譜のファイルとか .kif, .csa, バージョン違い、他、独自形式がいくつかある。
# ---- Kifu for Windows V6.00 棋譜ファイル ----
開始日時:2007/03/21
終了日時:2007/03/21
持ち時間:2時間
棋戦:大和証券杯特別対局
戦型:四間飛車
場所:品川プリンスホテル,クラブ eX
手合割:平手
先手:Bonanza
後手:渡辺明
手数----指手---------消費時間--
1 7六歩(77) ( 0:01/00:00:01)
2 8四歩(83) ( 2:32/00:02:32)
3 6六歩(67) ( 0:01/00:00:02)
4 3四歩(33) ( 0:11/00:02:43)
5 6八飛(28) ( 0:01/00:00:03)
6 6二銀(71) ( 0:14/00:02:57)
7 4八玉(59) ( 0:01/00:00:04)
8 5四歩(53) ( 0:17/00:03:14)
9 3八玉(48) ( 0:01/00:00:05)
10 4二玉(51) ( 0:20/00:03:34)
以下略
これの変換プログラムも Blunder(ブランダー)のGitHubや、柿木(かきのき)将棋のホームぺージに置いてあるが、コンパイルしようとしたら あれやこれや 揃えるのが
めんどくさいんで、それでは作ったものを プログラマー以外の人に使ってもらうにも大変。 設定不要のパーサーを自分でイチから作る。
じゃあ、好きな欲望のレベルを選べだぜ。
- バトル的な理由から、将棋の指し手だけ欲しい
- エンターテイメント的な理由から、対局場などの情報も欲しい
- ヒストリカル的な理由から、ファイルを読み込んで、書きだしたら、ファイルが完全一致するような 可逆性 があるものが欲しい
など、理由が いろいろ あれば、方法も それぞれある。ここでは バトル を選択する。
装備品
目: バトルの目
目を装備すると、今まで景色だったものは、素材に変わる。
1 7六歩(77) ( 0:01/00:00:01)
2 8四歩(83) ( 2:32/00:02:32)
3 6六歩(67) ( 0:01/00:00:02)
4 3四歩(33) ( 0:11/00:02:43)
上記のような 手数で始まってるとこだけ 欲しくて、
# ---- Kifu for Windows V6.00 棋譜ファイル ----
開始日時:2007/03/21
終了日時:2007/03/21
持ち時間:2時間
上記のようなヘッダーは要らない☆
欲しいもの、捨てていいものがはっきりすると、そのあとの行動もスマートになる。
仕様書がどこに落ちてあるのか見つからなかったので、
実装を経験的に調べてみると
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
上記の36パターンで始まる行は 将棋の指し手のようだ。
疑似コードで書くと
foreach line in file
{
if line.starts_with(" 1") | line.starts_with(" 2") | line.starts_with(" 3")
| line.starts_with(" 4") | | line.starts_with(" 5") | line.starts_with(" 6")
| line.starts_with(" 7") 以下略)
{
# ここに処理を書く
}
}
というコピペ技や、
foreach line in file
{
line <= line.trim_begin()
if try_parse_int(line[0])
{
# ここに処理を書く
}
}
といった 数字の出現判定だけでいけそうだ。
Rust言語で文字列パーサーを書いているんだが、「始」が 3byte で めんどくさい。1文字切り取るのがめんどくさい。
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '開' (bytes 0..3) of `開始日時:2007/03/21`', libcore\str\mod.rs:2111:5
パーサー以前に 文字列が めんどくさい。
Finished dev [unoptimized + debuginfo] target(s) in 1.76s
Running `target\debug\examples\eat_a_kif.exe --path C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif`
args.path = 'C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif'.
Ignored: # ---- Kifu for Windows V6.00 棋譜ファイル ----
Ignored: 開始日時:2007/03/21
Ignored: 終了日時:2007/03/21
Ignored: 持ち時間:2時間
Ignored: 棋戦:大和証券杯特別対局
Ignored: 戦型:四間飛車
Ignored: 場所:品川プリンスホテル,クラブ eX
Ignored: 手合割:平手
Ignored: 先手:Bonanza
Ignored: 後手:渡辺明
Ignored: 手数----指手---------消費時間--
Move : 1 7六歩(77) ( 0:01/00:00:01)
Move : 2 8四歩(83) ( 2:32/00:02:32)
Move : 3 6六歩(67) ( 0:01/00:00:02)
Move : 4 3四歩(33) ( 0:11/00:02:43)
Move : 5 6八飛(28) ( 0:01/00:00:03)
Move : 6 6二銀(71) ( 0:14/00:02:57)
Move : 7 4八玉(59) ( 0:01/00:00:04)
Move : 8 5四歩(53) ( 0:17/00:03:14)
Move : 9 3八玉(48) ( 0:01/00:00:05)
Move : 10 4二玉(51) ( 0:20/00:03:34)
指し手と判定すれば Move :
、 それ以外は Ignored:
を頭に付けて デバッグライト(debug write) する。
手数も消費時間も 今回は欲しくないので、
1 7六歩(77) ( 0:01/00:00:01)
ではなく
7六歩(77)
だけ欲しい。
"\s*\d+ ((.*))*\([ 0-9:/]+\)"
group[1]
ぐらい書けば 引っこ抜いてくれそうだが、実装的な説明は省略して そういうことは できたものとして話しを進める。
正規表現の説明は、めんどくせ、Debuggex 見て知れ。
正規表現を簡単に書くテクニックは、
.*(.*)?.*
この形から、
.*\s*(.*)?.*
.*\s*\d+(.*)?.*
と、正規表現をグラフィカルに見れるツールの画面を見ながら マッチするところを広げていけだぜ。
あっ!
50 4二金(32) ( 0:40/00:13:43)
51 6六角打 ( 2:15/00:39:01)
打のとき 丸かっこ が外れるのか。
47 6五歩(66) ( 2:07/00:36:45)
48 7七角成(33) ( 1:14/00:13:03)
49 同 桂(89) ( 0:01/00:36:46)
50 4二金(32) ( 0:40/00:13:43)
51 6六角打 ( 2:15/00:39:01)
周りを見ると けっこう 書式がばらばらだ。
80 3九成香(49) ( 0:55/01:01:22)
駒の名前も1文字だけではなく 成香
のように2文字のもある。
Finished dev [unoptimized + debuginfo] target(s) in 1.90s
Running `target\debug\examples\eat_a_kif.exe --path C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif`
args.path = 'C:/muzudho/kifuwarabe-wcsc29-learn/output-wcsc-record/copied-daiwa/daiwa.kif'.
Ignored: # ---- Kifu for Windows V6.00 棋譜ファイル ----
Ignored: 開始日時:2007/03/21
Ignored: 終了日時:2007/03/21
Ignored: 持ち時間:2時間
Ignored: 棋戦:大和証券杯特別対局
Ignored: 戦型:四間飛車
Ignored: 場所:品川プリンスホテル,クラブ eX
Ignored: 手合割:平手
Ignored: 先手:Bonanza
Ignored: 後手:渡辺明
Ignored: 手数----指手---------消費時間--
7六歩(77)
8四歩(83)
6六歩(67)
3四歩(33)
6八飛(28)
6二銀(71)
4八玉(59)
5四歩(53)
3八玉(48)
4二玉(51)
欲しいところが、欲しいところだけ 取れるようになる。ここまでが シンタックスパース(構文解析)、レキシカルパース(字句解析)フェーズだぜ。
だが、このままでは使いにくい。
6五歩(66)
7七角成(33)
同 桂(89)
4二金(32)
6六角打
この変化を 1パターンに おさめておきたい。
筋 段 同 駒 成 打 ( 筋 段 )
-- -- -- -- -- -- - - - -
6 五 歩 ( 6 6 )
7 七 角 成 ( 3 3 )
同 桂 ( 8 9 )
6 六 角 打
分解すると こうなるようだ。
Phase 0 1 2 3 4 5 6 7 8 9
筋 段 同 駒 成 打 ( 筋 段 )
-- -- -- -- -- -- - - - -
6 五 歩 ( 6 6 )
7 七 角 成 ( 3 3 )
同 桂 ( 8 9 )
6 六 角 打
フェーズの番号を振ると 10。 逐次 マッチングしていけばいい。
struct Sasite {
file : number,
rank : number,
same : bool,
piece : string,
promote : bool,
drop : bool,
file2 : number,
rank2 : number,
}
説明するのがめんどくさいので こういう適当な入れ物を作っておくと思えだぜ。
この入れ物を埋めていけばいい。
疑似コード書くの めんどくさいので Rust言語そのまま張り付ける。
use parser::*;
use piece_etc::*;
use regex::Regex;
pub struct KifMove {
pub dst_file : i8,
pub dst_rank : i8,
pub is_same : bool,
pub piece : String,
pub is_promote : bool,
pub is_drop : bool,
pub src_file : i8,
pub src_rank : i8,
}
impl KifMove {
pub fn to_sign(&self) -> String {
let mut sign = "".to_string();
sign = format!("{} {}", sign, self.dst_file);
sign = format!("{} {}", sign, self.dst_rank);
sign = format!("{} {}", sign, self.is_same);
sign = format!("{} {}", sign, self.piece);
sign = format!("{} {}", sign, self.is_promote);
sign = format!("{} {}", sign, self.is_drop);
sign = format!("{} {}", sign, self.src_file);
sign = format!("{} {}", sign, self.src_rank);
sign
}
pub fn parse(line:&str) -> Option<KifMove> {
let re = Regex::new(r"\s*\d+ ((.*))*\([ 0-9:/]+\)").unwrap();
let caps = re.captures(line).unwrap();
let sign = caps.get(1).map_or("", |m| m.as_str());
print!("{} -> ", sign);
let mut mv = KifMove {
dst_file : 0,
dst_rank : 0,
is_same : false,
piece : "".to_string(),
is_promote : false,
is_drop : false,
src_file : 0,
src_rank : 0,
};
/**
Phase 0 1 2 3 4 5 6 7 8 9
筋 段 同 駒 成 打 ( 筋 段 )
-- -- -- -- -- -- - - - -
6 五 歩 ( 6 6 )
7 七 角 成 ( 3 3 )
同 桂 ( 8 9 )
6 六 角 打
*/
let mut nth = 0;
if 0 < sign.len() - nth {
// Phase 0.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.dst_file = match ch.as_str() {
"1" => 1,
"2" => 2,
"3" => 3,
"4" => 4,
"5" => 5,
"6" => 6,
"7" => 7,
"8" => 8,
"9" => 9,
_ => {nth -= 1; 0},
};
}
if 0 < sign.len() - nth {
// Phase 1.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.dst_rank = match ch.as_str() {
"一" => 1,
"二" => 2,
"三" => 3,
"四" => 4,
"五" => 5,
"六" => 6,
"七" => 7,
"八" => 8,
"九" => 9,
_ => {nth -= 1; 0},
};
}
if 0 < sign.len() - nth {
// Phase 2.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.is_same = match ch.as_str() {
"同" => {
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
match ch.as_str() {
" " => true,
_ => panic!("Unexpected same ch: '{}'.", ch),
}
},
_ => {nth -= 1; false},
};
}
if 0 < sign.len() - nth {
// Phase 3.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.piece = match ch.as_str() {
"歩" => "歩",
"香" => "香",
"桂" => "桂",
"銀" => "銀",
"金" => "金",
"玉" => "玉",
"角" => "角",
"飛" => "飛",
"と" => "と",
"馬" => "馬",
"龍" => "龍",
"成" => {
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
match ch.as_str() {
"香" => "成香",
"桂" => "成桂",
"銀" => "成銀",
"金" => "成金",
_ => panic!("Unexpected promoted ch: '{}'.", ch),
}
},
_ => {nth -= 1; ""},
}.to_string();
}
if 0 < sign.len() - nth {
// Phase 4.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.is_promote = match ch.as_str() {
"成" => true,
_ => {nth -= 1; false},
};
}
if 0 < sign.len() - nth {
// Phase 5.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.is_drop = match ch.as_str() {
"打" => true,
_ => {nth -= 1; false},
};
}
if 0 < sign.len() - nth {
// Phase 6.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
match ch.as_str() {
"(" => {},
_ => {nth -= 1},
};
}
if 0 < sign.len() - nth {
// Phase 7.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.src_file = match ch.as_str() {
"1" => 1,
"2" => 2,
"3" => 3,
"4" => 4,
"5" => 5,
"6" => 6,
"7" => 7,
"8" => 8,
"9" => 9,
_ => {nth -= 1; 0},
};
}
if 0 < sign.len() - nth {
// Phase 8.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
mv.src_rank = match ch.as_str() {
"1" => 1,
"2" => 2,
"3" => 3,
"4" => 4,
"5" => 5,
"6" => 6,
"7" => 7,
"8" => 8,
"9" => 9,
_ => {nth -= 1; 0},
};
}
if 0 < sign.len() - nth {
// Phase 9.
let ch = sign.chars().nth(nth).unwrap().to_string();
nth += 1;
match ch.as_str() {
")" => {},
_ => {nth -= 1},
};
}
Some(mv)
}
}
だいたい動くだろ。
同 飛(27) -> : 0 0 true 飛 false false 2 7
1五金打 -> : 1 5 false 金 false true 0 0
2四歩打 -> : 2 4 false 歩 false true 0 0
2六金(15) -> : 2 6 false 金 false false 1 5
2三歩成(24) -> : 2 3 false 歩 true false 2 4
同 金(32) -> : 0 0 true 金 false false 3 2
2四歩打 -> : 2 4 false 歩 false true 0 0
2七歩打 -> : 2 7 false 歩 false true 0 0
2三歩成(24) -> : 2 3 false 歩 true false 2 4
3九龍(99) -> : 3 9 false 龍 false false 9 9
2二と(23) -> : 2 2 false と false false 2 3
同 角(33) -> : 0 0 true 角 false false 3 3
3九銀(28) -> : 3 9 false 銀 false false 2 8
2八金打 -> : 2 8 false 金 false true 0 0
同 銀(39) -> : 0 0 true 銀 false false 3 9
同 歩成(27) -> : 0 0 true 歩 true false 2 7
同 馬(91) -> : 0 0 true 馬 false false 9 1
2七歩打 -> : 2 7 false 歩 false true 0 0
同 馬(28) -> : 0 0 true 馬 false false 2 8
同 金(26) -> : 0 0 true 金 false false 2 6
3九銀打 -> : 3 9 false 銀 false true 0 0
3八銀打 -> : 3 8 false 銀 false true 0 0
2八金打 -> : 2 8 false 金 false true 0 0
同 金(27) -> : 0 0 true 金 false false 2 7
同 銀(39) -> : 0 0 true 銀 false false 3 9
2七歩打 -> : 2 7 false 歩 false true 0 0
ぱっと見た感じ それっぽい感じで取れている。
ここらへんは まだ レキシカルパース(字句解析)☆
2八金打 -> : 2 8 false 金 false true 0 0
同 銀(39) -> : 0 0 true 銀 false false 3 9
同 歩成(27) -> : 0 0 true 歩 true false 2 7
同 馬(91) -> : 0 0 true 馬 false false 9 1
同
が連続するところを見ていただきたい。
同は 何を示している 代わりの記号なのか。 というと 同が始まる最初の1個前の 2八 をずっと続けることだが、
レキシカルパースの中で 一緒に 同
も解決してしまうのではなく、
レキシカルパースの結果を使って 同
を解決することにする。
その理由としては、バグったときに ここまではバグっていない、ここから先でバグった、と問題を切り分けることができて バグを探しやすい ことだぜ。
// '同'を解決する。
let mut pre_file = 0;
let mut pre_rank = 0;
for mov in &mut record.items {
if mov.is_same {
mov.dst_file = pre_file;
mov.dst_rank = pre_rank;
}
pre_file = mov.dst_file;
pre_rank = mov.dst_rank;
println!("Move : {}", mov.to_sign());
}
実行速度を優先するのは 実行速度が遅いと思ってるやつが 完成しているときでいい。
Move : 6 7 false 飛 false false 6 8
Move : 8 6 false 歩 false false 8 5
Move : 8 6 true 歩 false false 8 7
Move : 8 6 true 飛 false false 8 3
Move : 9 1 false 角 true false 4 6
Move : 8 9 false 飛 true false 8 6
Move : 4 4 false 歩 false false 4 5
Move : 4 4 true 銀 false false 5 3
Move : 4 7 false 飛 false false 6 7
Move : 9 9 false 龍 false false 8 9
Move : 6 4 false 歩 false false 6 5
Move : 3 5 false 銀 false false 4 4
Move : 3 6 false 歩 false false 3 7
Move : 4 6 false 香 false true 0 0
Move : 3 7 false 飛 false false 4 7
Move : 2 6 false 銀 false false 3 5
Move : 2 6 true 銀 false false 2 7
Move : 4 9 false 香 true false 4 6
Move : 6 3 false 歩 true false 6 4
Move : 3 9 false 成香 false false 4 9
Move : 3 9 true 金 false false 3 8
Move : 2 5 false 歩 false false 2 4
Move : 2 5 true 銀 false false 2 6
Move : 2 7 false 歩 false true 0 0
Move : 2 7 true 飛 false false 3 7
Move : 2 6 false 歩 false true 0 0
Move : 2 6 true 飛 false false 2 7
Move : 1 5 false 金 false true 0 0
Move : 2 4 false 歩 false true 0 0
Move : 2 6 false 金 false false 1 5
ぱっと見た感じ、
3列目が true になっている行は、1列目、2列目の数が 上の行と同じになっているだろ。
このように テストをしながら
できている ところ
を増やして 進めていくのが開発だぜ。
これだけ変換できれば 将棋の駒を動かすことができる。 棋譜の解析はおわり。
途中っぽいが 終わり。
保存形式のコンバーターはどうやって作るかだって?
将棋盤で駒を動かせるんだったら 棋譜 は作れるだろ。
好きな形式のファイルを読み込んで、好きな形式のファイルで保存しろだぜ。
べつに どの形式から どの形式へ直接コンバートするっぽいやつは作らなくていい。ピボット!