sgf_tool/
lib.rs

1#[doc = include_str!("../README.md")]
2mod builder;
3mod parser;
4
5use std::borrow::Cow;
6
7pub use builder::build;
8pub use builder::Builder;
9pub use parser::parse;
10
11use serde::{Deserialize, Serialize};
12
13use strum::EnumDiscriminants;
14use strum::EnumMessage;
15use thiserror::Error;
16
17#[derive(Error, Debug, Serialize, Deserialize, Clone, PartialEq)]
18pub enum SgfToolError {
19    #[error("Syntax issue")]
20    SyntaxIssue,
21
22    #[error("Root object not found")]
23    RootObjectNotFound,
24
25    #[error("Parse failed")]
26    ParseFailed,
27
28    #[error("Invalid number")]
29    InvalidNumber,
30
31    #[error("Invalid string")]
32    InvalidString,
33
34    #[error("Invalid float")]
35    InvalidFloat,
36
37    #[error("Player information not valid")]
38    PlayerInformationNotValid,
39
40    #[error("Point information not valid")]
41    PointInformationNotValid,
42
43    #[error("Node information not valid")]
44    NodeInformationNotValid,
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
48pub struct Point<'a>(pub &'a str);
49
50impl Point<'_> {
51    pub fn xy(&self) -> (usize, usize) {
52        let position = self.0.to_lowercase();
53        let x = position.chars().nth(0).unwrap_or_default();
54        let y = position.chars().nth(1).unwrap_or_default();
55
56        (x as usize - 'a' as usize, y as usize - 'a' as usize)
57    }
58
59    pub fn xy_for_human(&self) -> (usize, usize) {
60        let (x, y) = self.xy();
61        (x + 1, y + 1)
62    }
63}
64
65#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
66pub enum Move<'a> {
67    Move(#[serde(borrow)] Point<'a>),
68    Pass,
69}
70
71#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
72pub struct PointRange<'a>(#[serde(borrow)] pub Point<'a>, pub Point<'a>);
73
74#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
75pub struct Figure<'a>(pub usize, pub &'a str);
76
77#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
78pub struct PointText<'a>(pub Point<'a>, pub &'a str);
79
80#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
81pub enum Player {
82    Black,
83    White,
84}
85
86#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
87pub struct Base<'a> {
88    #[serde(borrow)]
89    pub tokens: Vec<Cow<'a, Token<'a>>>,
90}
91
92impl<'a> Base<'a> {
93    pub fn add_token(&mut self, token: Token<'a>) {
94        self.tokens.push(Cow::Owned(token));
95    }
96
97    pub fn get(&self, token_type: TokenType) -> Option<&Cow<'a, Token<'a>>> {
98        self.tokens
99            .iter()
100            .find(|item| token_type == item.as_ref().into())
101    }
102
103    pub fn get_list(&self, token_type: TokenType) -> Vec<&Cow<'a, Token<'a>>> {
104        self.tokens
105            .iter()
106            .filter(|item| token_type == item.as_ref().into())
107            .collect::<Vec<_>>()
108    }
109}
110
111#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, EnumMessage, EnumDiscriminants)]
112#[strum_discriminants(name(TokenType))]
113pub enum Token<'a> {
114    Unknown(&'a str),
115
116    /// Property: AP
117    #[strum(message = "AP")]
118    Application(&'a str),
119
120    /// Property: C
121    #[strum(message = "C")]
122    Comment(&'a str),
123
124    /// Property: CP
125    #[strum(message = "CP")]
126    Copyright(&'a str),
127
128    /// Property: PB
129    #[strum(message = "PB")]
130    BlackName(&'a str),
131
132    /// Property: PW
133    #[strum(message = "PW")]
134    WhiteName(&'a str),
135
136    /// Property: BT
137    #[strum(message = "BT")]
138    BlackTeam(&'a str),
139
140    /// Property: WT
141    #[strum(message = "WT")]
142    WhiteTeam(&'a str),
143
144    /// Property: SZ
145    #[strum(message = "SZ")]
146    BoardSize(usize, usize),
147
148    /// Variation
149    Variation(Base<'a>),
150
151    /// Property: FF
152    #[strum(message = "FF")]
153    FileFormat(usize),
154
155    /// Property: GM
156    #[strum(message = "GM")]
157    GameType(usize),
158
159    /// Property: CA
160    #[strum(message = "CA")]
161    Charset(&'a str),
162
163    /// Property: ST
164    #[strum(message = "ST")]
165    VariationShown(usize),
166
167    /// Property: PL
168    #[strum(message = "PL")]
169    WhoseTurn(Player),
170
171    /// Property: AB
172    #[strum(message = "AB")]
173    BlackStones(Vec<Point<'a>>),
174
175    /// Property: AW
176    #[strum(message = "AW")]
177    WhiteStones(Vec<Point<'a>>),
178
179    /// Property: B
180    #[strum(message = "B")]
181    BlackMove(Move<'a>),
182
183    /// Property: W
184    #[strum(message = "W")]
185    WhiteMove(Move<'a>),
186
187    /// Property: BR
188    #[strum(message = "BR")]
189    BlackPlayerRank(&'a str),
190
191    /// Property: WR
192    #[strum(message = "WR")]
193    WhitePlayerRank(&'a str),
194
195    /// Property: SO
196    #[strum(message = "SO")]
197    Source(&'a str),
198
199    /// Property: GN
200    #[strum(message = "GN")]
201    GameName(&'a str),
202
203    /// Property: N
204    #[strum(message = "N")]
205    NodeName(&'a str),
206
207    /// Property: RU
208    #[strum(message = "RU")]
209    Rule(&'a str),
210
211    /// Property: KM
212    #[strum(message = "KM")]
213    Komi(f32),
214
215    /// Property: AN
216    #[strum(message = "AN")]
217    PersonWhoProvidesAnnotations(&'a str),
218
219    /// Property: AR
220    #[strum(message = "AR")]
221    DrawArrow(PointRange<'a>),
222
223    /// Property: CR
224    #[strum(message = "CR")]
225    DrawCircle(Vec<Point<'a>>),
226
227    /// Property: SQ
228    #[strum(message = "SQ")]
229    DrawSquare(Vec<Point<'a>>),
230
231    /// Property: TR
232    #[strum(message = "TR")]
233    DrawTriangle(Vec<Point<'a>>),
234
235    /// Property: DD
236    #[strum(message = "DD")]
237    GreyOut(Vec<Point<'a>>),
238
239    /// Property: MA
240    #[strum(message = "MA")]
241    MarkX(Vec<Point<'a>>),
242
243    /// Property: HA
244    #[strum(message = "HA")]
245    Handicap(usize),
246
247    /// Property: RE
248    #[strum(message = "RE")]
249    Result(&'a str),
250
251    /// Property: FG
252    #[strum(message = "FG")]
253    Figure(Option<Figure<'a>>),
254
255    /// Property: PM
256    #[strum(message = "PM")]
257    Printing(usize),
258
259    /// Property: TM
260    #[strum(message = "TM")]
261    TimeLimit(usize),
262
263    /// Property: DT
264    #[strum(message = "DT")]
265    Date(&'a str),
266
267    /// Property: AV
268    #[strum(message = "AV")]
269    Event(&'a str),
270
271    /// Property: LB
272    #[strum(message = "LB")]
273    PointText(Vec<PointText<'a>>),
274
275    /// Property: RO
276    #[strum(message = "RO")]
277    Round(&'a str),
278
279    /// Property: US
280    #[strum(message = "US")]
281    SGFCreator(&'a str),
282
283    /// Property: VW
284    #[strum(message = "VW")]
285    ViewOnly(Vec<PointRange<'a>>),
286
287    /// Property: MN
288    #[strum(message = "MN")]
289    MoveNumber(usize),
290}
291
292// More detail https://homepages.cwi.nl/~aeb/go/misc/sgf.html#GM
293// https://red-bean.com/sgf/properties.html#CR
294
295#[cfg(test)]
296mod tests {
297    use crate::builder::Builder;
298    use crate::parser::parse;
299    use crate::*;
300
301    #[test]
302    fn basic_sgf_parse() -> Result<(), SgfToolError> {
303        let result = parse("()")?;
304        assert_eq!(result.tokens.len(), 0);
305        assert_eq!(parse("(a)"), Err(SgfToolError::SyntaxIssue));
306        assert_eq!(parse("(1)"), Err(SgfToolError::SyntaxIssue));
307        assert_eq!(parse("("), Err(SgfToolError::SyntaxIssue));
308        assert_eq!(parse(")"), Err(SgfToolError::SyntaxIssue));
309        assert_eq!(parse(""), Err(SgfToolError::SyntaxIssue));
310        assert_eq!(parse("-"), Err(SgfToolError::SyntaxIssue));
311        assert_eq!(parse(" "), Err(SgfToolError::SyntaxIssue));
312        Ok(())
313    }
314
315    #[test]
316    fn sgf_parse() -> Result<(), SgfToolError> {
317        let result = parse(
318            r#"(
319    ;FF[4]
320    C[root]
321    (
322        ;C[a]
323        ;C[b]
324        (
325            ;C[c]
326        )
327        (
328            ;C[d]
329            ;C[e]
330        )
331    )
332    (
333        ;C[f]
334        (
335            ;C[g]
336            ;C[h]
337            ;C[i]
338        )
339        (
340            ;C[j]
341        )
342    )
343)"#,
344        )?;
345        assert_eq!(result.tokens.len(), 4);
346        assert_eq!(result.tokens[0].as_ref(), &Token::FileFormat(4));
347        assert_eq!(result.tokens[1].as_ref(), &Token::Comment("root"));
348
349        if let Token::Variation(trees) = result.tokens[2].as_ref() {
350            assert_eq!(trees.tokens.len(), 4);
351            assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("a"));
352            assert_eq!(trees.tokens[1].as_ref(), &Token::Comment("b"));
353
354            if let Token::Variation(trees) = trees.tokens[2].as_ref() {
355                assert_eq!(trees.tokens.len(), 1);
356                assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("c"));
357            } else {
358                assert!(false, "Variation not found");
359            }
360
361            if let Token::Variation(trees) = trees.tokens[3].as_ref() {
362                assert_eq!(trees.tokens.len(), 2);
363                assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("d"));
364                assert_eq!(trees.tokens[1].as_ref(), &Token::Comment("e"));
365            } else {
366                assert!(false, "Variation not found");
367            }
368        } else {
369            assert!(false, "Variation not found");
370        }
371
372        if let Token::Variation(trees) = &result.tokens[3].as_ref() {
373            assert_eq!(trees.tokens.len(), 3);
374            assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("f"));
375
376            if let Token::Variation(trees) = trees.tokens[1].as_ref() {
377                assert_eq!(trees.tokens.len(), 3);
378                assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("g"));
379                assert_eq!(trees.tokens[1].as_ref(), &Token::Comment("h"));
380                assert_eq!(trees.tokens[2].as_ref(), &Token::Comment("i"));
381            } else {
382                assert!(false, "Variation not found");
383            }
384
385            if let Token::Variation(trees) = trees.tokens[2].as_ref() {
386                assert_eq!(trees.tokens.len(), 1);
387                assert_eq!(trees.tokens[0].as_ref(), &Token::Comment("j"));
388            } else {
389                assert!(false, "Variation not found");
390            }
391        } else {
392            assert!(false, "Variation not found");
393        }
394
395        parse(
396            r#"
397        (;FF[4]GM[1]SZ[19]FG[257:Figure 1]PM[1]
398            PB[Takemiya Masaki]BR[9 dan]PW[Cho Chikun]
399            WR[9 dan]RE[W+Resign]KM[5.5]TM[28800]DT[1996-10-18,19]
400            EV[21st Meijin]RO[2 (final)]SO[Go World #78]US[Arno Hollosi]
401            ;B[pd];W[dp];B[pp];W[dd];B[pj];W[nc];B[oe];W[qc];B[pc];W[qd]
402            (;B[qf];W[rf];B[rg];W[re];B[qg];W[pb];B[ob];W[qb]
403            (;B[mp];W[fq];B[ci];W[cg];B[dl];W[cn];B[qo];W[ec];B[jp];W[jd]
404            ;B[ei];W[eg];B[kk]LB[qq:a][dj:b][ck:c][qp:d]N[Figure 1]
405            
406            ;W[me]FG[257:Figure 2];B[kf];W[ke];B[lf];W[jf];B[jg]
407            (;W[mf];B[if];W[je];B[ig];W[mg];B[mj];W[mq];B[lq];W[nq]
408            (;B[lr];W[qq];B[pq];W[pr];B[rq];W[rr];B[rp];W[oq];B[mr];W[oo];B[mn]
409            (;W[nr];B[qp]LB[kd:a][kh:b]N[Figure 2]
410            
411            ;W[pk]FG[257:Figure 3];B[pm];W[oj];B[ok];W[qr];B[os];W[ol];B[nk];W[qj]
412            ;B[pi];W[pl];B[qm];W[ns];B[sr];W[om];B[op];W[qi];B[oi]
413            (;W[rl];B[qh];W[rm];B[rn];W[ri];B[ql];W[qk];B[sm];W[sk];B[sh];W[og]
414            ;B[oh];W[np];B[no];W[mm];B[nn];W[lp];B[kp];W[lo];B[ln];W[ko];B[mo]
415            ;W[jo];B[km]N[Figure 3])
416            
417            (;W[ql]VW[ja:ss]FG[257:Dia. 6]MN[1];B[rm];W[ph];B[oh];W[pg];B[og];W[pf]
418            ;B[qh];W[qe];B[sh];W[of];B[sj]TR[oe][pd][pc][ob]LB[pe:a][sg:b][si:c]
419            N[Diagram 6]))
420            
421            (;W[no]VW[jj:ss]FG[257:Dia. 5]MN[1];B[pn]N[Diagram 5]))
422            
423            (;B[pr]FG[257:Dia. 4]MN[1];W[kq];B[lp];W[lr];B[jq];W[jr];B[kp];W[kr];B[ir]
424            ;W[hr]LB[is:a][js:b][or:c]N[Diagram 4]))
425            
426            (;W[if]FG[257:Dia. 3]MN[1];B[mf];W[ig];B[jh]LB[ki:a]N[Diagram 3]))
427            
428            (;W[oc]VW[aa:sk]FG[257:Dia. 2]MN[1];B[md];W[mc];B[ld]N[Diagram 2]))
429            
430            (;B[qe]VW[aa:sj]FG[257:Dia. 1]MN[1];W[re];B[qf];W[rf];B[qg];W[pb];B[ob]
431            ;W[qb]LB[rg:a]N[Diagram 1]))
432             "#,
433        )?;
434
435        parse(
436            r#"(;FF[4]GM[1]SZ[19]FG[257:Figure 1]PM[2]
437                PB[Cho Chikun]BR[9 dan]PW[Ryu Shikun]WR[9 dan]RE[W+2.5]KM[5.5]
438                DT[1996-08]EV[51st Honinbo]RO[5 (final)]SO[Go World #78]US[Arno Hollosi]
439                ;B[qd];W[dd];B[fc];W[df];B[pp];W[dq];B[kc];W[cn];B[pj];W[jp];B[lq];W[oe]
440                ;B[pf];W[ke];B[id];W[lc];B[lb];W[kb];B[jb];W[kd];B[ka];W[jc];B[ic];W[kb]
441                ;B[mc];W[qc]N[Figure 1]
442                
443                ;B[pd]FG[257:Figure 2];W[pc];B[od];W[oc];B[kc];W[nd];B[nc];W[kb];B[rd];W[pe]
444                (;B[rf];W[md];B[kc];W[qe];B[re];W[kb];B[mb];W[qf];B[qg];W[pg];B[qh];W[kc]
445                ;B[hb];W[nf];B[ch];W[cj];B[eh];W[ob]
446                (;B[cc];W[dc];B[db];W[bf];B[bb]
447                ;W[bh]LB[of:a][mf:b][rc:c][di:d][ja:e]N[Figure 2]
448                
449                ;B[qp]FG[257:Figure 3];W[lo];B[ej];W[oq]
450                (;B[np];W[mq];B[mp];W[lp]
451                (;B[kq];W[nq];B[op];W[jq];B[mr];W[nr];B[lr];W[qr];B[jr];W[ir];B[hr];W[iq]
452                ;B[is];W[ks];B[js];W[gq];B[gr];W[fq];B[pq];W[pr];B[ns];W[or];B[rq];W[hq]
453                ;B[rr];W[cl];B[cg];W[bg];B[og];W[ng]
454                (;B[ci];W[bi];B[dj];W[dk];B[mm];W[gk];B[gi];W[mn];B[nm];W[kl];B[nh];W[mh]
455                ;B[mi];W[li];B[lh];W[mg];B[ek];W[el];B[ik]LB[kr:a]N[Figure 3]
456                
457                ;W[ki]FG[257:Figure 4];B[fl];W[fk];B[gl];W[hk];B[hl];W[hj];B[jl];W[kk];B[km]
458                ;W[lm];B[ll];W[jm];B[jj];W[ji];B[kj];W[lj];B[ij];W[hi];B[em];W[dl];B[ii]
459                ;W[hh];B[ih];W[hg];B[ln];W[kn];B[lm];W[im];B[il];W[fg];B[lk];W[ni];B[ef]
460                ;W[eg];B[dg];W[ff];B[oh];W[of];B[oj];W[ph];B[oi];W[mj];B[ee];W[fe];B[de]
461                ;W[ed];B[ce];W[cf];B[rb];W[rc];B[sc];W[qb];B[sb];W[la];B[ma];W[na];B[ja]
462                ;W[nb];B[la];W[pa];B[be];W[fd];B[bj];W[ck];B[ec];W[hs];B[gs];W[fr];B[os]
463                ;W[ps];B[ms];W[nk];B[ok];W[kp];B[fo];W[fs];B[qq];W[hs];B[do];W[co];B[ig]
464                ;W[gc];B[gb];W[jf];B[di];W[fi];B[hf];W[gf];B[af];W[mo];B[he];W[kr];B[qs]
465                ;W[no];B[oo];W[nn];B[on];W[nl];B[ol];W[gn];B[fn];W[in];B[nj];W[mk];B[jg]
466                ;W[kg];B[mi];W[jh];B[ag];W[bk];B[ah];W[aj];B[fh];W[fj];B[gd];W[ra];B[dp]
467                ;W[cp];B[go];W[gm];B[fm];W[sd];B[se];W[ho];B[hm];W[hn];B[ep];W[eq];B[cd]
468                ;W[ei];B[dn];W[gp];B[pi];W[pf];B[dm];W[cm];B[je];W[jd];B[if];W[ie];B[ko]
469                ;W[jo];B[je];W[kf];B[ni];W[dh];B[ge];W[ie];B[rg];W[je]N[Figure 4])
470                
471                (;B[dk]FG[257:Dia. 6]MN[1];W[ck];B[gk]N[Diagram 6]))
472                
473                (;B[nq]VW[ai:ss]FG[257:Dia. 5]MN[1];W[mr];B[nr];W[lr]TR[oq]N[Diagram 5]))
474                
475                (;B[mp]VW[ai:ss]FG[257:Dia. 4]MN[1];W[op];B[oo];W[no];B[mo];W[on];B[po]
476                ;W[mn];B[np];W[nn];B[or]N[Diagram 4]))
477                
478                (;B[rc]VW[aa:sj]FG[257:Dia. 2]MN[1];W[rb];B[sb];W[la];B[ma];W[na];B[ja]
479                ;W[pa]N[Diagram 2])
480                
481                (;B[rb]VW[aa:sj]FG[257:Dia. 3]MN[1];W[rc];B[sc];W[qb];B[pa];W[sb];B[sa]
482                ;W[sd];B[qa]N[Diagram 3]))
483                
484                (;B[qf]VW[aa:sj]FG[257:Dia. 1]MN[1];W[mb];B[kc];W[qe];B[ne];W[kb];B[md]
485                ;W[la];B[nb];W[eb]LB[ob:a][na:b][rc:c][sd:d]N[Diagram 1]))
486                "#,
487        )?;
488
489        Ok(())
490    }
491
492    #[test]
493    fn basic_test_1() -> Result<(), SgfToolError> {
494        let source = "(;C[Black to play and win, Igo Hatsuyo-ron Problem 120];AB[ra][hb][lb][fc][lc][bd][ld][ce][de][fe][le][me][oe][pe][bf][mf][of][og][dh][oh][ph][qh][rh][sh][di][mi][ni][oi][pi][aj][fj][lj][ak][ek][lk][rk][sk][al][el][il][pl][ql][am][bm][em][qm][rm][dn][fn][mn][co][fo][ko][oo][bp][cp][ep][pp][sp][fq][pq][qq][sq][cr][nr][pr][bs][ns][os][ps];AW[qa][ib][jb][mb][rb][hc][qc][cd][jd][nd][od][ke][qe][re][df][ff][pf][bg][cg][dg][gg][hg][kg][lg][ng][ah][hi][ki][ri][si][bj][cj][jj][nj][oj][pj][qj][dk][jk][ok][dl][jl][rl][sl][hm][jm][sm][bn][cn][en][jn][on][rn][sn][qo][ro][so][ap][hp][kp][lp][mp][op][qp][eq][rq][ar][ir][mr][or][ms])";
495        let mut buffer = String::new();
496        let tree = parse(&source)?;
497        tree.build(&mut buffer)?;
498        assert_eq!(buffer, source);
499        Ok(())
500    }
501
502    #[test]
503    fn basic_test_2() -> Result<(), SgfToolError> {
504        let source = "(;AW[ca][cb][cc][bd][cd];AB[da][eb][dc][ec][dd][fd][be][ce][de];B[ab];W[bb];B[ac];W[ad];B[aa];C[RIGHT])";
505        let mut buffer = String::new();
506        let tree = parse(&source)?;
507        tree.build(&mut buffer)?;
508        assert_eq!(buffer, source);
509        Ok(())
510    }
511
512    #[test]
513    fn basic_test_3() -> Result<(), SgfToolError> {
514        let source = "(;AW[hh][lh][hi][ji][li][lj];AB[kg][lg][mg][mh][mi][mj][kk][lk][mk];C[Black to play and catch the three stones.](;B[ki];W[kh](;B[jh];W[kj];B[jj];W[ki];B[ii];C[RIGHT])(;B[kj];W[jh]))(;B[jh];W[jj])(;B[jj];W[jh])(;B[ii];W[jj]))";
515        let mut buffer = String::new();
516        let tree = parse(&source)?;
517        tree.build(&mut buffer)?;
518        assert_eq!(buffer, source);
519        Ok(())
520    }
521
522    #[test]
523    fn basic_test_4() -> Result<(), SgfToolError> {
524        let source = "(;FF[4];C[root];SZ[19];B[aa];W[ab];B[])";
525        let mut buffer = String::new();
526        let tree = parse(&source)?;
527        tree.build(&mut buffer)?;
528        assert_eq!(buffer, source);
529        Ok(())
530    }
531
532    #[test]
533    fn basic_test_5() -> Result<(), SgfToolError> {
534        let point = Point("ss");
535        assert_eq!(point.xy(), (18, 18));
536        assert_eq!(point.xy_for_human(), (19, 19));
537        Ok(())
538    }
539
540    #[test]
541    fn basic_test_6() -> Result<(), SgfToolError> {
542        let source = "(;FF[4];C[root];SZ[19];B[aa];W[ab];B[])";
543        let tree = parse(&source)?;
544
545        assert_eq!(
546            tree.get(TokenType::FileFormat),
547            Some(Cow::Owned(Token::FileFormat(4))).as_ref()
548        );
549        assert_eq!(
550            tree.get(TokenType::Comment),
551            Some(Cow::Owned(Token::Comment("root"))).as_ref()
552        );
553        assert_eq!(
554            tree.get(TokenType::BoardSize),
555            Some(Cow::Owned(Token::BoardSize(19, 19))).as_ref()
556        );
557        assert_eq!(
558            tree.get(TokenType::BlackMove),
559            Some(Cow::Owned(Token::BlackMove(Move::Move(Point("aa"))))).as_ref()
560        );
561
562        let items = tree.get_list(TokenType::BlackMove);
563        assert_eq!(items.len(), 2);
564        assert_eq!(
565            items.get(0),
566            Some(Cow::Owned(Token::BlackMove(Move::Move(Point("aa")))))
567                .as_ref()
568                .as_ref()
569        );
570        assert_eq!(
571            items.get(1),
572            Some(Cow::Owned(Token::BlackMove(Move::Pass)))
573                .as_ref()
574                .as_ref()
575        );
576        Ok(())
577    }
578}