Set up SGF reading and start on the game database #47

Merged
savanni merged 20 commits from kifu/sgf into main 2023-07-26 13:54:28 +00:00
4 changed files with 337 additions and 63 deletions
Showing only changes of commit 2469cd78fa - Show all commits

View File

@ -69,11 +69,11 @@
use nom::{ use nom::{
bytes::complete::{tag, take_until}, bytes::complete::{tag, take_until},
character::complete::{alpha1, anychar, multispace0}, character::complete::{alpha1, anychar, digit1, multispace0},
combinator::eof, combinator::eof,
multi::{many0, many1, many_till}, multi::{many0, many1, many_till, separated_list1},
sequence::{delimited, terminated}, sequence::{delimited, terminated},
IResult, Parser, Finish, IResult, Parser,
}; };
use thiserror::Error; use thiserror::Error;
@ -85,6 +85,12 @@ pub enum ParseError {
UnknownError, UnknownError,
} }
impl From<nom::error::Error<&str>> for ParseError {
fn from(_: nom::error::Error<&str>) -> Self {
Self::UnknownError
}
}
// todo: support ST root node // todo: support ST root node
#[derive(Debug)] #[derive(Debug)]
pub struct GameTree { pub struct GameTree {
@ -181,6 +187,37 @@ enum PropValue {
Stone, Stone,
} }
pub fn parse_sgf(input: &str) -> Result<GameTree, ParseError> {
let (_, tree) = parse_tree(input).finish()?;
let file_format = match tree.sequence[0].find_prop("FF") {
Some(prop) => prop.values[0].parse::<i8>().unwrap(),
None => 4,
};
let app = tree.sequence[0]
.find_prop("AP")
.map(|prop| prop.values[0].clone());
let board_size = match tree.sequence[0].find_prop("SZ") {
Some(prop) => {
let (_, size) = parse_size(prop.values[0].as_str()).finish()?;
size
}
None => Size {
width: 19,
height: 19,
},
};
Ok(GameTree {
file_format,
app,
game_type: GameType::Go,
board_size,
text: input.to_owned(),
})
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
struct Tree { struct Tree {
sequence: Vec<Node>, sequence: Vec<Node>,
@ -219,7 +256,16 @@ impl ToString for Node {
} }
} }
#[derive(Debug, PartialEq)] impl Node {
fn find_prop(&self, ident: &str) -> Option<Property> {
self.properties
.iter()
.find(|prop| prop.ident == ident)
.cloned()
}
}
#[derive(Clone, Debug, PartialEq)]
struct Property { struct Property {
ident: String, ident: String,
values: Vec<String>, values: Vec<String>,
@ -239,13 +285,6 @@ impl ToString for Property {
// note: must preserve unknown properties // note: must preserve unknown properties
// note: must fix or preserve illegally formatted game-info properties // note: must fix or preserve illegally formatted game-info properties
// note: must correct or delete illegally foramtted properties, but display a warning // note: must correct or delete illegally foramtted properties, but display a warning
/*
pub fn parse_sgf(input: &str) -> Result<(GameTree, Vec<Warning>), ParseError> {
let (_, gameinfo) = parse_gametree(input).unwrap();
Ok((gameinfo, vec![]))
}
*/
fn parse_tree(input: &str) -> IResult<&str, Tree> { fn parse_tree(input: &str) -> IResult<&str, Tree> {
println!("parse_tree: {}", input); println!("parse_tree: {}", input);
let (input, _) = multispace0(input)?; let (input, _) = multispace0(input)?;
@ -277,6 +316,7 @@ fn parse_node(input: &str) -> IResult<&str, Node> {
fn parse_property(input: &str) -> IResult<&str, Property> { fn parse_property(input: &str) -> IResult<&str, Property> {
println!("parse_property: {}", input); println!("parse_property: {}", input);
let (input, _) = multispace0(input)?;
let (input, ident) = alpha1(input)?; let (input, ident) = alpha1(input)?;
let (input, values) = many1(delimited(tag("["), take_until("]"), tag("]")))(input)?; let (input, values) = many1(delimited(tag("["), take_until("]"), tag("]")))(input)?;
@ -293,49 +333,30 @@ fn parse_property(input: &str) -> IResult<&str, Property> {
)) ))
} }
/* fn parse_size(input: &str) -> IResult<&str, Size> {
fn parse_gametree(input: &str) -> IResult<&str, GameTree> { let (input, dimensions) = separated_list1(tag(":"), digit1)(input)?;
let (input, _) = tag("(;")(input)?; let (width, height) = match dimensions.as_slice() {
let (input, properties) = many1(parse_property)(input)?; [width] => (width.parse::<i32>().unwrap(), width.parse::<i32>().unwrap()),
let (input, _) = tag(")")(input)?; [width, height] => (
println!("properties: {:?}", properties); width.parse::<i32>().unwrap(),
Ok((input, unimplemented!())) height.parse::<i32>().unwrap(),
} ),
_ => (19, 19),
*/ };
Ok((input, Size { width, height }))
pub fn add(left: usize, right: usize) -> usize {
left + right
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{fs::File, io::Read};
use super::*; use super::*;
const EXAMPLE_1: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c]) const EXAMPLE: &'static str = "(;FF[4]C[root](;C[a];C[b](;C[c])
(;C[d];C[e])) (;C[d];C[e]))
(;C[f](;C[g];C[h];C[i]) (;C[f](;C[g];C[h];C[i])
(;C[j])))"; (;C[j])))";
const EXAMPLE_2: &'static str = "(;FF[4]GM[1]SZ[19]AP[SGFC:1.13b]
PB[troy]BR[12k*]
PW[john]WR[11k*]
KM[0.5]RE[W+12.5]
DT[1998-06-15]
TM[600]
;B[pd];W[dp];B[pq];W[dd];B[qk];W[jd];B[fq];W[dj];B[jp];W[jj]
;B[cn]LB[dn:A][po:B]C[dada: other ideas are 'A' (d6) or 'B' (q5)]
;W[eo](;B[dl]C[dada: hm - looks troublesome.
Usually B plays the 3,3 invasion - see variation];W[qo];B[qp]
...
;W[sr];B[sk];W[sg];B[pa];W[gc];B[pi];W[ph];B[de];W[ed];B[kn]
;W[dh];B[eh];W[se];B[sd];W[af];B[ie];W[id];B[hf];W[hd];B[if]
;W[fp];B[gq];W[qj];B[sj];W[rh];B[sn];W[so];B[sm];W[ep];B[mn])
...
(;W[dq]N[wrong direction];B[qo];W[qp]))";
#[test] #[test]
fn it_can_parse_properties() { fn it_can_parse_properties() {
let (_, prop) = parse_property("C[a]").unwrap(); let (_, prop) = parse_property("C[a]").unwrap();
@ -479,7 +500,7 @@ Usually B plays the 3,3 invasion - see variation];W[qo];B[qp]
#[test] #[test]
fn it_can_parse_example_1() { fn it_can_parse_example_1() {
let (_, ex_tree) = parse_tree(EXAMPLE_1).unwrap(); let (_, ex_tree) = parse_tree(EXAMPLE).unwrap();
assert_eq!(ex_tree.sequence.len(), 1); assert_eq!(ex_tree.sequence.len(), 1);
assert_eq!(ex_tree.sequence[0].properties.len(), 2); assert_eq!(ex_tree.sequence[0].properties.len(), 2);
@ -515,7 +536,7 @@ Usually B plays the 3,3 invasion - see variation];W[qo];B[qp]
#[test] #[test]
fn it_can_regenerate_the_tree() { fn it_can_regenerate_the_tree() {
let (_, tree1) = parse_tree(EXAMPLE_1).unwrap(); let (_, tree1) = parse_tree(EXAMPLE).unwrap();
println!("{}", tree1.to_string()); println!("{}", tree1.to_string());
assert_eq!( assert_eq!(
tree1.to_string(), tree1.to_string(),
@ -525,41 +546,44 @@ Usually B plays the 3,3 invasion - see variation];W[qo];B[qp]
assert_eq!(tree1, tree2); assert_eq!(tree1, tree2);
} }
/* fn with_text(text: &str, f: impl FnOnce(GameTree)) {
fn with_examples(f: impl FnOnce(GameTree, GameTree)) { f(parse_sgf(text).unwrap());
let (example_1, _) = parse_sgf(EXAMPLE_1).unwrap(); }
let (example_2, _) = parse_sgf(EXAMPLE_2).unwrap();
f(example_1, example_2); fn with_file(path: &std::path::Path, f: impl FnOnce(GameTree)) {
let mut file = File::open(path).unwrap();
let mut text = String::new();
let _ = file.read_to_string(&mut text);
with_text(&text, f);
} }
#[test] #[test]
fn it_parses_game_root() { fn it_parses_game_root() {
with_examples(|ex_1, ex_2| { with_text(EXAMPLE, |tree| {
assert_eq!(ex_1.file_format, 4); assert_eq!(tree.file_format, 4);
assert_eq!(ex_1.app, None); assert_eq!(tree.app, None);
assert_eq!(ex_1.game_type, GameType::Go); assert_eq!(tree.game_type, GameType::Go);
assert_eq!( assert_eq!(
ex_1.board_size, tree.board_size,
Size { Size {
width: 19, width: 19,
height: 19 height: 19
} }
); );
assert_eq!(ex_1.text, EXAMPLE_1.to_owned()); assert_eq!(tree.text, EXAMPLE.to_owned());
});
assert_eq!(ex_2.file_format, 4); with_file(std::path::Path::new("test_data/print1.sgf"), |tree| {
assert_eq!(ex_2.app, Some("SGFC:1.13b".to_owned())); assert_eq!(tree.file_format, 4);
assert_eq!(ex_2.game_type, GameType::Go); assert_eq!(tree.app, None);
assert_eq!(tree.game_type, GameType::Go);
assert_eq!( assert_eq!(
ex_2.board_size, tree.board_size,
Size { Size {
width: 19, width: 19,
height: 19 height: 19
} }
); );
assert_eq!(ex_2.text, EXAMPLE_2.to_owned());
}); });
} }
*/
} }

165
go-sgf/test_data/ff4_ex.sgf Normal file
View File

@ -0,0 +1,165 @@
(;FF[4]AP[Primiview:3.1]GM[1]SZ[19]GN[Gametree 1: properties]US[Arno Hollosi]
(;B[pd]N[Moves, comments, annotations]
C[Nodename set to: "Moves, comments, annotations"];W[dp]GW[1]
C[Marked as "Good for White"];B[pp]GB[2]
C[Marked as "Very good for Black"];W[dc]GW[2]
C[Marked as "Very good for White"];B[pj]DM[1]
C[Marked as "Even position"];W[ci]UC[1]
C[Marked as "Unclear position"];B[jd]TE[1]
C[Marked as "Tesuji" or "Good move"];W[jp]BM[2]
C[Marked as "Very bad move"];B[gd]DO[]
C[Marked as "Doubtful move"];W[de]IT[]
C[Marked as "Interesting move"];B[jj];
C[White "Pass" move]W[];
C[Black "Pass" move]B[tt])
(;AB[dd][de][df][dg][do:gq]
AW[jd][je][jf][jg][kn:lq][pn:pq]
N[Setup]C[Black & white stones at the top are added as single stones.
Black & white stones at the bottom are added using compressed point lists.]
;AE[ep][fp][kn][lo][lq][pn:pq]
C[AddEmpty
Black stones & stones of left white group are erased in FF[3\] way.
White stones at bottom right were erased using compressed point list.]
;AB[pd]AW[pp]PL[B]C[Added two stones.
Node marked with "Black to play".];PL[W]
C[Node marked with "White to play"])
(;AB[dd][de][df][dg][dh][di][dj][nj][ni][nh][nf][ne][nd][ij][ii][ih][hq]
[gq][fq][eq][dr][ds][dq][dp][cp][bp][ap][iq][ir][is][bo][bn][an][ms][mr]
AW[pd][pe][pf][pg][ph][pi][pj][fd][fe][ff][fh][fi][fj][kh][ki][kj][os][or]
[oq][op][pp][qp][rp][sp][ro][rn][sn][nq][mq][lq][kq][kr][ks][fs][gs][gr]
[er]N[Markup]C[Position set up without compressed point lists.]
;TR[dd][de][df][ed][ee][ef][fd:ff]
MA[dh][di][dj][ej][ei][eh][fh:fj]
CR[nd][ne][nf][od][oe][of][pd:pf]
SQ[nh][ni][nj][oh][oi][oj][ph:pj]
SL[ih][ii][ij][jj][ji][jh][kh:kj]
TW[pq:ss][so][lr:ns]
TB[aq:cs][er:hs][ao]
C[Markup at top partially using compressed point lists (for markup on white stones); listed clockwise, starting at upper left:
- TR (triangle)
- CR (circle)
- SQ (square)
- SL (selected points)
- MA ('X')
Markup at bottom: black & white territory (using compressed point lists)]
;LB[dc:1][fc:2][nc:3][pc:4][dj:a][fj:b][nj:c]
[pj:d][gs:ABCDEFGH][gr:ABCDEFG][gq:ABCDEF][gp:ABCDE][go:ABCD][gn:ABC][gm:AB]
[mm:12][mn:123][mo:1234][mp:12345][mq:123456][mr:1234567][ms:12345678]
C[Label (LB property)
Top: 8 single char labels (1-4, a-d)
Bottom: Labels up to 8 char length.]
;DD[kq:os][dq:hs]
AR[aa:sc][sa:ac][aa:sa][aa:ac][cd:cj]
[gd:md][fh:ij][kj:nh]
LN[pj:pd][nf:ff][ih:fj][kh:nj]
C[Arrows, lines and dimmed points.])
(;B[qd]N[Style & text type]
C[There are hard linebreaks & soft linebreaks.
Soft linebreaks are linebreaks preceeded by '\\' like this one >o\
k<. Hard line breaks are all other linebreaks.
Soft linebreaks are converted to >nothing<, i.e. removed.
Note that linebreaks are coded differently on different systems.
Examples (>ok< shouldn't be split):
linebreak 1 "\\n": >o\
k<
linebreak 2 "\\n\\r": >o\
k<
linebreak 3 "\\r\\n": >o\
k<
linebreak 4 "\\r": >o\ k<]
(;W[dd]N[W d16]C[Variation C is better.](;B[pp]N[B q4])
(;B[dp]N[B d4])
(;B[pq]N[B q3])
(;B[oq]N[B p3])
)
(;W[dp]N[W d4])
(;W[pp]N[W q4])
(;W[cc]N[W c17])
(;W[cq]N[W c3])
(;W[qq]N[W r3])
)
(;B[qr]N[Time limits, captures & move numbers]
BL[120.0]C[Black time left: 120 sec];W[rr]
WL[300]C[White time left: 300 sec];B[rq]
BL[105.6]OB[10]C[Black time left: 105.6 sec
Black stones left (in this byo-yomi period): 10];W[qq]
WL[200]OW[2]C[White time left: 200 sec
White stones left: 2];B[sr]
BL[87.00]OB[9]C[Black time left: 87 sec
Black stones left: 9];W[qs]
WL[13.20]OW[1]C[White time left: 13.2 sec
White stones left: 1];B[rs]
C[One white stone at s2 captured];W[ps];B[pr];W[or]
MN[2]C[Set move number to 2];B[os]
C[Two white stones captured
(at q1 & r1)]
;MN[112]W[pq]C[Set move number to 112];B[sq];W[rp];B[ps]
;W[ns];B[ss];W[nr]
;B[rr];W[sp];B[qs]C[Suicide move
(all B stones get captured)])
)
(;FF[4]AP[Primiview:3.1]GM[1]SZ[19]C[Gametree 2: game-info
Game-info properties are usually stored in the root node.
If games are merged into a single game-tree, they are stored in the node\
where the game first becomes distinguishable from all other games in\
the tree.]
;B[pd]
(;PW[W. Hite]WR[6d]RO[2]RE[W+3.5]
PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[dp]
C[Game-info:
Black: B. Lack, 5d
White: W. Hite, 6d
Place: London
Event: Go Congress
Round: 2
Result: White wins by 3.5])
(;PW[T. Suji]WR[7d]RO[1]RE[W+Resign]
PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[cp]
C[Game-info:
Black: B. Lack, 5d
White: T. Suji, 7d
Place: London
Event: Go Congress
Round: 1
Result: White wins by resignation])
(;W[ep];B[pp]
(;PW[S. Abaki]WR[1d]RO[3]RE[B+63.5]
PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[ed]
C[Game-info:
Black: B. Lack, 5d
White: S. Abaki, 1d
Place: London
Event: Go Congress
Round: 3
Result: Balck wins by 63.5])
(;PW[A. Tari]WR[12k]KM[-59.5]RO[4]RE[B+R]
PB[B. Lack]BR[5d]PC[London]EV[Go Congress]W[cd]
C[Game-info:
Black: B. Lack, 5d
White: A. Tari, 12k
Place: London
Event: Go Congress
Round: 4
Komi: -59.5 points
Result: Black wins by resignation])
))

View File

@ -0,0 +1,35 @@
(;FF[4]GM[1]SZ[19]FG[257:Figure 1]PM[1]
PB[Takemiya Masaki]BR[9 dan]PW[Cho Chikun]
WR[9 dan]RE[W+Resign]KM[5.5]TM[28800]DT[1996-10-18,19]
EV[21st Meijin]RO[2 (final)]SO[Go World #78]US[Arno Hollosi]
;B[pd];W[dp];B[pp];W[dd];B[pj];W[nc];B[oe];W[qc];B[pc];W[qd]
(;B[qf];W[rf];B[rg];W[re];B[qg];W[pb];B[ob];W[qb]
(;B[mp];W[fq];B[ci];W[cg];B[dl];W[cn];B[qo];W[ec];B[jp];W[jd]
;B[ei];W[eg];B[kk]LB[qq:a][dj:b][ck:c][qp:d]N[Figure 1]
;W[me]FG[257:Figure 2];B[kf];W[ke];B[lf];W[jf];B[jg]
(;W[mf];B[if];W[je];B[ig];W[mg];B[mj];W[mq];B[lq];W[nq]
(;B[lr];W[qq];B[pq];W[pr];B[rq];W[rr];B[rp];W[oq];B[mr];W[oo];B[mn]
(;W[nr];B[qp]LB[kd:a][kh:b]N[Figure 2]
;W[pk]FG[257:Figure 3];B[pm];W[oj];B[ok];W[qr];B[os];W[ol];B[nk];W[qj]
;B[pi];W[pl];B[qm];W[ns];B[sr];W[om];B[op];W[qi];B[oi]
(;W[rl];B[qh];W[rm];B[rn];W[ri];B[ql];W[qk];B[sm];W[sk];B[sh];W[og]
;B[oh];W[np];B[no];W[mm];B[nn];W[lp];B[kp];W[lo];B[ln];W[ko];B[mo]
;W[jo];B[km]N[Figure 3])
(;W[ql]VW[ja:ss]FG[257:Dia. 6]MN[1];B[rm];W[ph];B[oh];W[pg];B[og];W[pf]
;B[qh];W[qe];B[sh];W[of];B[sj]TR[oe][pd][pc][ob]LB[pe:a][sg:b][si:c]
N[Diagram 6]))
(;W[no]VW[jj:ss]FG[257:Dia. 5]MN[1];B[pn]N[Diagram 5]))
(;B[pr]FG[257:Dia. 4]MN[1];W[kq];B[lp];W[lr];B[jq];W[jr];B[kp];W[kr];B[ir]
;W[hr]LB[is:a][js:b][or:c]N[Diagram 4]))
(;W[if]FG[257:Dia. 3]MN[1];B[mf];W[ig];B[jh]LB[ki:a]N[Diagram 3]))
(;W[oc]VW[aa:sk]FG[257:Dia. 2]MN[1];B[md];W[mc];B[ld]N[Diagram 2]))
(;B[qe]VW[aa:sj]FG[257:Dia. 1]MN[1];W[re];B[qf];W[rf];B[qg];W[pb];B[ob]
;W[qb]LB[rg:a]N[Diagram 1]))

View File

@ -0,0 +1,50 @@
(;FF[4]GM[1]SZ[19]FG[257:Figure 1]PM[2]
PB[Cho Chikun]BR[9 dan]PW[Ryu Shikun]WR[9 dan]RE[W+2.5]KM[5.5]
DT[1996-08]EV[51st Honinbo]RO[5 (final)]SO[Go World #78]US[Arno Hollosi]
;B[qd];W[dd];B[fc];W[df];B[pp];W[dq];B[kc];W[cn];B[pj];W[jp];B[lq];W[oe]
;B[pf];W[ke];B[id];W[lc];B[lb];W[kb];B[jb];W[kd];B[ka];W[jc];B[ic];W[kb]
;B[mc];W[qc]N[Figure 1]
;B[pd]FG[257:Figure 2];W[pc];B[od];W[oc];B[kc];W[nd];B[nc];W[kb];B[rd];W[pe]
(;B[rf];W[md];B[kc];W[qe];B[re];W[kb];B[mb];W[qf];B[qg];W[pg];B[qh];W[kc]
;B[hb];W[nf];B[ch];W[cj];B[eh];W[ob]
(;B[cc];W[dc];B[db];W[bf];B[bb]
;W[bh]LB[of:a][mf:b][rc:c][di:d][ja:e]N[Figure 2]
;B[qp]FG[257:Figure 3];W[lo];B[ej];W[oq]
(;B[np];W[mq];B[mp];W[lp]
(;B[kq];W[nq];B[op];W[jq];B[mr];W[nr];B[lr];W[qr];B[jr];W[ir];B[hr];W[iq]
;B[is];W[ks];B[js];W[gq];B[gr];W[fq];B[pq];W[pr];B[ns];W[or];B[rq];W[hq]
;B[rr];W[cl];B[cg];W[bg];B[og];W[ng]
(;B[ci];W[bi];B[dj];W[dk];B[mm];W[gk];B[gi];W[mn];B[nm];W[kl];B[nh];W[mh]
;B[mi];W[li];B[lh];W[mg];B[ek];W[el];B[ik]LB[kr:a]N[Figure 3]
;W[ki]FG[257:Figure 4];B[fl];W[fk];B[gl];W[hk];B[hl];W[hj];B[jl];W[kk];B[km]
;W[lm];B[ll];W[jm];B[jj];W[ji];B[kj];W[lj];B[ij];W[hi];B[em];W[dl];B[ii]
;W[hh];B[ih];W[hg];B[ln];W[kn];B[lm];W[im];B[il];W[fg];B[lk];W[ni];B[ef]
;W[eg];B[dg];W[ff];B[oh];W[of];B[oj];W[ph];B[oi];W[mj];B[ee];W[fe];B[de]
;W[ed];B[ce];W[cf];B[rb];W[rc];B[sc];W[qb];B[sb];W[la];B[ma];W[na];B[ja]
;W[nb];B[la];W[pa];B[be];W[fd];B[bj];W[ck];B[ec];W[hs];B[gs];W[fr];B[os]
;W[ps];B[ms];W[nk];B[ok];W[kp];B[fo];W[fs];B[qq];W[hs];B[do];W[co];B[ig]
;W[gc];B[gb];W[jf];B[di];W[fi];B[hf];W[gf];B[af];W[mo];B[he];W[kr];B[qs]
;W[no];B[oo];W[nn];B[on];W[nl];B[ol];W[gn];B[fn];W[in];B[nj];W[mk];B[jg]
;W[kg];B[mi];W[jh];B[ag];W[bk];B[ah];W[aj];B[fh];W[fj];B[gd];W[ra];B[dp]
;W[cp];B[go];W[gm];B[fm];W[sd];B[se];W[ho];B[hm];W[hn];B[ep];W[eq];B[cd]
;W[ei];B[dn];W[gp];B[pi];W[pf];B[dm];W[cm];B[je];W[jd];B[if];W[ie];B[ko]
;W[jo];B[je];W[kf];B[ni];W[dh];B[ge];W[ie];B[rg];W[je]N[Figure 4])
(;B[dk]FG[257:Dia. 6]MN[1];W[ck];B[gk]N[Diagram 6]))
(;B[nq]VW[ai:ss]FG[257:Dia. 5]MN[1];W[mr];B[nr];W[lr]TR[oq]N[Diagram 5]))
(;B[mp]VW[ai:ss]FG[257:Dia. 4]MN[1];W[op];B[oo];W[no];B[mo];W[on];B[po]
;W[mn];B[np];W[nn];B[or]N[Diagram 4]))
(;B[rc]VW[aa:sj]FG[257:Dia. 2]MN[1];W[rb];B[sb];W[la];B[ma];W[na];B[ja]
;W[pa]N[Diagram 2])
(;B[rb]VW[aa:sj]FG[257:Dia. 3]MN[1];W[rc];B[sc];W[qb];B[pa];W[sb];B[sa]
;W[sd];B[qa]N[Diagram 3]))
(;B[qf]VW[aa:sj]FG[257:Dia. 1]MN[1];W[mb];B[kc];W[qe];B[ne];W[kb];B[md]
;W[la];B[nb];W[eb]LB[ob:a][na:b][rc:c][sd:d]N[Diagram 1]))