csa/parser/
game.rs

1use nom::branch::alt;
2use nom::bytes::complete::{is_a, is_not, tag, take};
3use nom::character::complete::{anychar, digit1, one_of};
4use nom::combinator::{map, map_res, opt, value};
5use nom::multi::{count, many0, separated_list0};
6use nom::sequence::{delimited, pair, preceded, separated_pair, terminated, tuple};
7use nom::*;
8use std::str;
9use std::time::Duration;
10
11use super::time::{datetime, timelimit};
12use crate::value::*;
13
14fn line_sep(input: &[u8]) -> IResult<&[u8], &[u8]> {
15    is_a("\r\n,")(input)
16}
17
18fn not_line_sep(input: &[u8]) -> IResult<&[u8], &[u8]> {
19    is_not("\r\n,")(input)
20}
21
22fn comment(input: &[u8]) -> IResult<&[u8], &[u8]> {
23    preceded(tag("'"), not_line_sep)(input)
24}
25
26fn comment_line(input: &[u8]) -> IResult<&[u8], &[u8]> {
27    terminated(comment, line_sep)(input)
28}
29
30fn color(input: &[u8]) -> IResult<&[u8], Color> {
31    map(one_of("+-"), |s| match s {
32        '+' => Color::Black,
33        _ => Color::White,
34    })(input)
35}
36
37fn decimal(input: &[u8]) -> IResult<&[u8], Duration> {
38    map_res(digit1, |s| {
39        str::from_utf8(s)
40            .map(|s| s.parse::<u64>().unwrap())
41            .map(Duration::from_secs)
42    })(input)
43}
44
45fn fu(input: &[u8]) -> IResult<&[u8], PieceType> {
46    value(PieceType::Pawn, tag("FU"))(input)
47}
48
49fn ky(input: &[u8]) -> IResult<&[u8], PieceType> {
50    value(PieceType::Lance, tag("KY"))(input)
51}
52
53fn ke(input: &[u8]) -> IResult<&[u8], PieceType> {
54    value(PieceType::Knight, tag("KE"))(input)
55}
56
57fn gi(input: &[u8]) -> IResult<&[u8], PieceType> {
58    value(PieceType::Silver, tag("GI"))(input)
59}
60
61fn ki(input: &[u8]) -> IResult<&[u8], PieceType> {
62    value(PieceType::Gold, tag("KI"))(input)
63}
64
65fn ka(input: &[u8]) -> IResult<&[u8], PieceType> {
66    value(PieceType::Bishop, tag("KA"))(input)
67}
68
69fn hi(input: &[u8]) -> IResult<&[u8], PieceType> {
70    value(PieceType::Rook, tag("HI"))(input)
71}
72
73fn ou(input: &[u8]) -> IResult<&[u8], PieceType> {
74    value(PieceType::King, tag("OU"))(input)
75}
76
77fn to(input: &[u8]) -> IResult<&[u8], PieceType> {
78    value(PieceType::ProPawn, tag("TO"))(input)
79}
80
81fn ny(input: &[u8]) -> IResult<&[u8], PieceType> {
82    value(PieceType::ProLance, tag("NY"))(input)
83}
84
85fn nk(input: &[u8]) -> IResult<&[u8], PieceType> {
86    value(PieceType::ProKnight, tag("NK"))(input)
87}
88
89fn ng(input: &[u8]) -> IResult<&[u8], PieceType> {
90    value(PieceType::ProSilver, tag("NG"))(input)
91}
92
93fn um(input: &[u8]) -> IResult<&[u8], PieceType> {
94    value(PieceType::Horse, tag("UM"))(input)
95}
96
97fn ry(input: &[u8]) -> IResult<&[u8], PieceType> {
98    value(PieceType::Dragon, tag("RY"))(input)
99}
100
101fn al(input: &[u8]) -> IResult<&[u8], PieceType> {
102    value(PieceType::All, tag("AL"))(input)
103}
104
105fn piece_type(input: &[u8]) -> IResult<&[u8], PieceType> {
106    alt((fu, ky, ke, gi, ki, ka, hi, ou, to, ny, nk, ng, um, ry, al))(input)
107}
108
109fn one_digit(input: &[u8]) -> IResult<&[u8], u8> {
110    map(one_of("0123456789"), |c: char| {
111        c.to_digit(10).unwrap() as u8
112    })(input)
113}
114
115fn square(input: &[u8]) -> IResult<&[u8], Square> {
116    map(tuple((one_digit, one_digit)), |(file, rank)| {
117        Square::new(file, rank)
118    })(input)
119}
120
121fn version(input: &[u8]) -> IResult<&[u8], &[u8]> {
122    preceded(tag("V"), alt((tag("2.1"), tag("2.2"), tag("2"))))(input)
123}
124
125fn black_player(input: &[u8]) -> IResult<&[u8], &[u8]> {
126    preceded(tag("N+"), not_line_sep)(input)
127}
128
129fn white_player(input: &[u8]) -> IResult<&[u8], &[u8]> {
130    preceded(tag("N-"), not_line_sep)(input)
131}
132
133fn game_text_attr(input: &[u8]) -> IResult<&[u8], GameAttribute> {
134    map(map_res(not_line_sep, str::from_utf8), |s: &str| {
135        GameAttribute::Str(s.to_string())
136    })(input)
137}
138
139fn game_time_attr(input: &[u8]) -> IResult<&[u8], GameAttribute> {
140    map(datetime, GameAttribute::Time)(input)
141}
142
143fn game_timelimit_attr(input: &[u8]) -> IResult<&[u8], GameAttribute> {
144    map(timelimit, GameAttribute::TimeLimit)(input)
145}
146
147fn game_attr(input: &[u8]) -> IResult<&[u8], (String, GameAttribute)> {
148    preceded(
149        tag("$"),
150        separated_pair(
151            map_res(is_not(":"), |s: &[u8]| String::from_utf8(s.to_vec())),
152            tag(":"),
153            alt((game_time_attr, game_timelimit_attr, game_text_attr)),
154        ),
155    )(input)
156}
157
158fn handicap(input: &[u8]) -> IResult<&[u8], Vec<(Square, PieceType)>> {
159    preceded(tag("PI"), many0(tuple((square, piece_type))))(input)
160}
161
162fn grid_piece(input: &[u8]) -> IResult<&[u8], Option<(Color, PieceType)>> {
163    let (input, result) = anychar(input)?;
164
165    match result {
166        '+' => map(piece_type, |pt| Some((Color::Black, pt)))(input),
167        '-' => map(piece_type, |pt| Some((Color::White, pt)))(input),
168        _ => value(None, take(2usize))(input),
169    }
170}
171
172type GridRow = [Option<(Color, PieceType)>; 9];
173type Grid = [GridRow; 9];
174
175fn grid_row(input: &[u8]) -> IResult<&[u8], GridRow> {
176    let (input, vec) = count(grid_piece, 9)(input)?;
177
178    // TODO: Convert into an array instead of copying.
179    let mut array = [None; 9];
180    array.clone_from_slice(&vec);
181
182    Ok((input, array))
183}
184
185fn grid(input: &[u8]) -> IResult<&[u8], Grid> {
186    let (input, r1) = delimited(tag("P1"), grid_row, line_sep)(input)?;
187    let (input, r2) = delimited(tag("P2"), grid_row, line_sep)(input)?;
188    let (input, r3) = delimited(tag("P3"), grid_row, line_sep)(input)?;
189    let (input, r4) = delimited(tag("P4"), grid_row, line_sep)(input)?;
190    let (input, r5) = delimited(tag("P5"), grid_row, line_sep)(input)?;
191    let (input, r6) = delimited(tag("P6"), grid_row, line_sep)(input)?;
192    let (input, r7) = delimited(tag("P7"), grid_row, line_sep)(input)?;
193    let (input, r8) = delimited(tag("P8"), grid_row, line_sep)(input)?;
194    let (input, r9) = preceded(tag("P9"), grid_row)(input)?;
195
196    Ok((input, [r1, r2, r3, r4, r5, r6, r7, r8, r9]))
197}
198
199fn piece_placement(input: &[u8]) -> IResult<&[u8], Vec<(Color, Square, PieceType)>> {
200    let (input, _) = tag("P")(input)?;
201    let (input, c) = color(input)?;
202    let (input, pcs) = many0(tuple((square, piece_type)))(input)?;
203
204    Ok((
205        input,
206        pcs.iter().map(|&(sq, pt)| (c, sq, pt)).collect::<Vec<_>>(),
207    ))
208}
209
210fn normal_move(input: &[u8]) -> IResult<&[u8], Action> {
211    let (input, c) = color(input)?;
212    let (input, from) = square(input)?;
213    let (input, to) = square(input)?;
214    let (input, pt) = piece_type(input)?;
215
216    Ok((input, Action::Move(c, from, to, pt)))
217}
218
219fn special_move(input: &[u8]) -> IResult<&[u8], Action> {
220    preceded(
221        tag("%"),
222        alt((
223            value(Action::Toryo, tag("TORYO")),
224            value(Action::Matta, tag("MATTA")),
225            value(Action::Tsumi, tag("TSUMI")),
226            value(Action::Error, tag("ERROR")),
227            value(Action::Kachi, tag("KACHI")),
228            value(Action::Chudan, tag("CHUDAN")),
229            value(Action::Fuzumi, tag("FUZUMI")),
230            value(Action::Jishogi, tag("JISHOGI")),
231            value(Action::Hikiwake, tag("HIKIWAKE")),
232            value(Action::Sennichite, tag("SENNICHITE")),
233        )),
234    )(input)
235}
236
237fn move_record(input: &[u8]) -> IResult<&[u8], MoveRecord> {
238    let (input, action) = alt((normal_move, special_move))(input)?;
239    let (input, time) = opt(preceded(line_sep, preceded(tag("T"), decimal)))(input)?;
240
241    Ok((input, MoveRecord { action, time }))
242}
243
244fn move_records(input: &[u8]) -> IResult<&[u8], Vec<MoveRecord>> {
245    let (input, moves) = many0(map(
246        pair(terminated(move_record, line_sep), many0(comment_line)),
247        |(m, _)| m,
248    ))(input)?;
249
250    Ok((input, moves))
251}
252
253pub fn game_record(input: &[u8]) -> IResult<&[u8], GameRecord> {
254    let (input, _) = many0(comment_line)(input)?;
255    let (input, _) = opt(terminated(version, line_sep))(input)?;
256    let (input, _) = many0(comment_line)(input)?;
257    let (input, black_player) = opt(map_res(terminated(black_player, line_sep), |b| {
258        str::from_utf8(b)
259    }))(input)?;
260    let (input, _) = many0(comment_line)(input)?;
261    let (input, white_player) = opt(map_res(terminated(white_player, line_sep), |b| {
262        str::from_utf8(b)
263    }))(input)?;
264    let (input, _) = many0(comment_line)(input)?;
265    let (input, attrs) = map(
266        opt(terminated(
267            separated_list0(line_sep, preceded(many0(comment_line), game_attr)),
268            line_sep,
269        )),
270        |v: Option<Vec<(String, GameAttribute)>>| v.unwrap_or_default(),
271    )(input)?;
272    let (input, _) = many0(comment_line)(input)?;
273    let (input, drop_pieces) = opt(terminated(handicap, line_sep))(input)?;
274    let (input, _) = many0(comment_line)(input)?;
275    let (input, bulk) = opt(terminated(grid, line_sep))(input)?;
276    let (input, _) = many0(comment_line)(input)?;
277    let (input, add_pieces) = many0(terminated(piece_placement, line_sep))(input)?;
278    let (input, _) = many0(comment_line)(input)?;
279    let (input, side_to_move) = terminated(color, line_sep)(input)?;
280    let (input, _) = many0(comment_line)(input)?;
281    let (input, moves) = move_records(input)?;
282
283    Ok((
284        input,
285        GameRecord {
286            black_player: black_player.map(|s| s.to_string()),
287            white_player: white_player.map(|s| s.to_string()),
288            event: attrs
289                .iter()
290                .find(|pair| pair.0 == "EVENT")
291                .map(|pair| pair.1.to_string()),
292            site: attrs
293                .iter()
294                .find(|pair| pair.0 == "SITE")
295                .map(|pair| pair.1.to_string()),
296            start_time: attrs.iter().find(|pair| pair.0 == "START_TIME").and_then(
297                |pair| match pair.1 {
298                    GameAttribute::Time(ref t) => Some(t.clone()),
299                    _ => None,
300                },
301            ),
302            end_time: attrs
303                .iter()
304                .find(|pair| pair.0 == "END_TIME")
305                .and_then(|pair| match pair.1 {
306                    GameAttribute::Time(ref t) => Some(t.clone()),
307                    _ => None,
308                }),
309            time_limit: attrs.iter().find(|pair| pair.0 == "TIME_LIMIT").and_then(
310                |pair| match pair.1 {
311                    GameAttribute::TimeLimit(ref t) => Some(t.clone()),
312                    _ => None,
313                },
314            ),
315            opening: attrs
316                .iter()
317                .find(|pair| pair.0 == "OPENING")
318                .map(|pair| pair.1.to_string()),
319            start_pos: Position {
320                drop_pieces: drop_pieces.unwrap_or_default(),
321                bulk,
322                add_pieces: add_pieces.into_iter().flatten().collect(),
323                side_to_move,
324            },
325            moves,
326        },
327    ))
328}
329
330////////////////////////////////////////////////////////////////////////////////
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    use time::{Date as NativeDate, Time as NativeTime};
337
338    #[test]
339    fn parse_comment() {
340        assert_eq!(
341            comment(b"'this is a comment"),
342            Result::Ok((&b""[..], &b"this is a comment"[..]))
343        );
344    }
345
346    #[test]
347    fn parse_piece_type() {
348        assert_eq!(piece_type(b"FU"), Result::Ok((&b""[..], PieceType::Pawn)));
349        assert_eq!(piece_type(b"KY"), Result::Ok((&b""[..], PieceType::Lance)));
350        assert_eq!(piece_type(b"KE"), Result::Ok((&b""[..], PieceType::Knight)));
351        assert_eq!(piece_type(b"GI"), Result::Ok((&b""[..], PieceType::Silver)));
352        assert_eq!(piece_type(b"KI"), Result::Ok((&b""[..], PieceType::Gold)));
353        assert_eq!(piece_type(b"KA"), Result::Ok((&b""[..], PieceType::Bishop)));
354        assert_eq!(piece_type(b"HI"), Result::Ok((&b""[..], PieceType::Rook)));
355        assert_eq!(piece_type(b"OU"), Result::Ok((&b""[..], PieceType::King)));
356        assert_eq!(
357            piece_type(b"TO"),
358            Result::Ok((&b""[..], PieceType::ProPawn))
359        );
360        assert_eq!(
361            piece_type(b"NY"),
362            Result::Ok((&b""[..], PieceType::ProLance))
363        );
364        assert_eq!(
365            piece_type(b"NK"),
366            Result::Ok((&b""[..], PieceType::ProKnight))
367        );
368        assert_eq!(
369            piece_type(b"NG"),
370            Result::Ok((&b""[..], PieceType::ProSilver))
371        );
372        assert_eq!(piece_type(b"UM"), Result::Ok((&b""[..], PieceType::Horse)));
373        assert_eq!(piece_type(b"RY"), Result::Ok((&b""[..], PieceType::Dragon)));
374    }
375
376    #[test]
377    fn parse_one_digit() {
378        assert_eq!(one_digit(b"0"), Result::Ok((&b""[..], 0)));
379        assert_eq!(one_digit(b"1"), Result::Ok((&b""[..], 1)));
380        assert_eq!(one_digit(b"2"), Result::Ok((&b""[..], 2)));
381        assert_eq!(one_digit(b"3"), Result::Ok((&b""[..], 3)));
382        assert_eq!(one_digit(b"4"), Result::Ok((&b""[..], 4)));
383        assert_eq!(one_digit(b"5"), Result::Ok((&b""[..], 5)));
384        assert_eq!(one_digit(b"6"), Result::Ok((&b""[..], 6)));
385        assert_eq!(one_digit(b"7"), Result::Ok((&b""[..], 7)));
386        assert_eq!(one_digit(b"8"), Result::Ok((&b""[..], 8)));
387        assert_eq!(one_digit(b"9"), Result::Ok((&b""[..], 9)));
388        assert_eq!(one_digit(b"10"), Result::Ok((&b"0"[..], 1)));
389    }
390
391    #[test]
392    fn parse_square() {
393        assert_eq!(square(b"00"), Result::Ok((&b""[..], Square::new(0, 0))));
394        assert_eq!(square(b"99"), Result::Ok((&b""[..], Square::new(9, 9))));
395    }
396
397    #[test]
398    fn parse_version() {
399        assert_eq!(version(b"V2"), Result::Ok((&b""[..], &b"2"[..])));
400        assert_eq!(version(b"V2.1"), Result::Ok((&b""[..], &b"2.1"[..])));
401        assert_eq!(version(b"V2.2"), Result::Ok((&b""[..], &b"2.2"[..])));
402    }
403
404    #[test]
405    fn parse_players() {
406        assert_eq!(
407            black_player(b"N+black player"),
408            Result::Ok((&b""[..], &b"black player"[..]))
409        );
410        assert_eq!(
411            white_player(b"N-white player"),
412            Result::Ok((&b""[..], &b"white player"[..]))
413        );
414    }
415
416    #[test]
417    fn parse_game_attr() {
418        assert_eq!(
419            game_attr(b"$EVENT:event"),
420            Result::Ok((
421                &b""[..],
422                ("EVENT".to_string(), GameAttribute::Str("event".to_string()))
423            ))
424        );
425        assert_eq!(
426            game_attr(b"$START_TIME:2002/01/01 19:00:00"),
427            Result::Ok((
428                &b""[..],
429                (
430                    "START_TIME".to_string(),
431                    GameAttribute::Time(Time {
432                        date: NativeDate::from_calendar_date(2002, time::Month::January, 1)
433                            .unwrap(),
434                        time: Some(NativeTime::from_hms(19, 0, 0).unwrap())
435                    })
436                )
437            ))
438        );
439    }
440
441    #[test]
442    fn parse_handicap() {
443        assert_eq!(handicap(b"PI"), Result::Ok((&b""[..], vec![])));
444        assert_eq!(
445            handicap(b"PI82HI22KA"),
446            Result::Ok((
447                &b""[..],
448                vec![
449                    (Square::new(8, 2), PieceType::Rook),
450                    (Square::new(2, 2), PieceType::Bishop)
451                ]
452            ))
453        );
454    }
455
456    #[test]
457    fn parse_grid() {
458        let grid_str = b"\
459P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
460P2 * -HI *  *  *  *  * -KA * 
461P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
462P4 *  *  *  *  *  *  *  *  * 
463P5 *  *  *  *  *  *  *  *  * 
464P6 *  *  *  *  *  *  *  *  * 
465P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
466P8 * +KA *  *  *  *  * +HI * 
467P9+KY+KE+GI+KI+OU+KI+GI+KE+KY";
468
469        let initial_pos = [
470            [
471                Some((Color::White, PieceType::Lance)),
472                Some((Color::White, PieceType::Knight)),
473                Some((Color::White, PieceType::Silver)),
474                Some((Color::White, PieceType::Gold)),
475                Some((Color::White, PieceType::King)),
476                Some((Color::White, PieceType::Gold)),
477                Some((Color::White, PieceType::Silver)),
478                Some((Color::White, PieceType::Knight)),
479                Some((Color::White, PieceType::Lance)),
480            ],
481            [
482                None,
483                Some((Color::White, PieceType::Rook)),
484                None,
485                None,
486                None,
487                None,
488                None,
489                Some((Color::White, PieceType::Bishop)),
490                None,
491            ],
492            [
493                Some((Color::White, PieceType::Pawn)),
494                Some((Color::White, PieceType::Pawn)),
495                Some((Color::White, PieceType::Pawn)),
496                Some((Color::White, PieceType::Pawn)),
497                Some((Color::White, PieceType::Pawn)),
498                Some((Color::White, PieceType::Pawn)),
499                Some((Color::White, PieceType::Pawn)),
500                Some((Color::White, PieceType::Pawn)),
501                Some((Color::White, PieceType::Pawn)),
502            ],
503            [None, None, None, None, None, None, None, None, None],
504            [None, None, None, None, None, None, None, None, None],
505            [None, None, None, None, None, None, None, None, None],
506            [
507                Some((Color::Black, PieceType::Pawn)),
508                Some((Color::Black, PieceType::Pawn)),
509                Some((Color::Black, PieceType::Pawn)),
510                Some((Color::Black, PieceType::Pawn)),
511                Some((Color::Black, PieceType::Pawn)),
512                Some((Color::Black, PieceType::Pawn)),
513                Some((Color::Black, PieceType::Pawn)),
514                Some((Color::Black, PieceType::Pawn)),
515                Some((Color::Black, PieceType::Pawn)),
516            ],
517            [
518                None,
519                Some((Color::Black, PieceType::Bishop)),
520                None,
521                None,
522                None,
523                None,
524                None,
525                Some((Color::Black, PieceType::Rook)),
526                None,
527            ],
528            [
529                Some((Color::Black, PieceType::Lance)),
530                Some((Color::Black, PieceType::Knight)),
531                Some((Color::Black, PieceType::Silver)),
532                Some((Color::Black, PieceType::Gold)),
533                Some((Color::Black, PieceType::King)),
534                Some((Color::Black, PieceType::Gold)),
535                Some((Color::Black, PieceType::Silver)),
536                Some((Color::Black, PieceType::Knight)),
537                Some((Color::Black, PieceType::Lance)),
538            ],
539        ];
540
541        assert_eq!(grid(grid_str), Result::Ok((&b""[..], initial_pos)));
542    }
543
544    #[test]
545    fn parse_piece_placement() {
546        assert_eq!(
547            piece_placement(b"P+99KY89KE"),
548            Result::Ok((
549                &b""[..],
550                vec![
551                    (Color::Black, Square::new(9, 9), PieceType::Lance),
552                    (Color::Black, Square::new(8, 9), PieceType::Knight),
553                ]
554            ))
555        );
556        assert_eq!(
557            piece_placement(b"P-00AL"),
558            Result::Ok((
559                &b""[..],
560                vec![(Color::White, Square::new(0, 0), PieceType::All),]
561            ))
562        );
563    }
564
565    #[test]
566    fn parse_normal_move() {
567        assert_eq!(
568            normal_move(b"+2726FU"),
569            Result::Ok((
570                &b""[..],
571                Action::Move(
572                    Color::Black,
573                    Square::new(2, 7),
574                    Square::new(2, 6),
575                    PieceType::Pawn
576                )
577            ))
578        );
579        assert_eq!(
580            normal_move(b"-3334FU"),
581            Result::Ok((
582                &b""[..],
583                Action::Move(
584                    Color::White,
585                    Square::new(3, 3),
586                    Square::new(3, 4),
587                    PieceType::Pawn
588                )
589            ))
590        );
591    }
592
593    #[test]
594    fn parse_special_move() {
595        assert_eq!(
596            special_move(b"%TORYO"),
597            Result::Ok((&b""[..], Action::Toryo))
598        );
599        assert_eq!(
600            special_move(b"%MATTA"),
601            Result::Ok((&b""[..], Action::Matta))
602        );
603        assert_eq!(
604            special_move(b"%TSUMI"),
605            Result::Ok((&b""[..], Action::Tsumi))
606        );
607        assert_eq!(
608            special_move(b"%ERROR"),
609            Result::Ok((&b""[..], Action::Error))
610        );
611        assert_eq!(
612            special_move(b"%KACHI"),
613            Result::Ok((&b""[..], Action::Kachi))
614        );
615        assert_eq!(
616            special_move(b"%CHUDAN"),
617            Result::Ok((&b""[..], Action::Chudan))
618        );
619        assert_eq!(
620            special_move(b"%FUZUMI"),
621            Result::Ok((&b""[..], Action::Fuzumi))
622        );
623        assert_eq!(
624            special_move(b"%JISHOGI"),
625            Result::Ok((&b""[..], Action::Jishogi))
626        );
627        assert_eq!(
628            special_move(b"%HIKIWAKE"),
629            Result::Ok((&b""[..], Action::Hikiwake))
630        );
631        assert_eq!(
632            special_move(b"%SENNICHITE"),
633            Result::Ok((&b""[..], Action::Sennichite))
634        );
635    }
636
637    #[test]
638    fn parse_move_record() {
639        assert_eq!(
640            move_record(b"+2726FU\nT5"),
641            Result::Ok((
642                &b""[..],
643                MoveRecord {
644                    action: Action::Move(
645                        Color::Black,
646                        Square::new(2, 7),
647                        Square::new(2, 6),
648                        PieceType::Pawn
649                    ),
650                    time: Some(Duration::from_secs(5))
651                }
652            ))
653        );
654        assert_eq!(
655            move_record(b"+2726FU"),
656            Result::Ok((
657                &b""[..],
658                MoveRecord {
659                    action: Action::Move(
660                        Color::Black,
661                        Square::new(2, 7),
662                        Square::new(2, 6),
663                        PieceType::Pawn
664                    ),
665                    time: None
666                }
667            ))
668        );
669
670        assert_eq!(
671            move_record(b"%TORYO\nT5"),
672            Result::Ok((
673                &b""[..],
674                MoveRecord {
675                    action: Action::Toryo,
676                    time: Some(Duration::from_secs(5))
677                }
678            ))
679        );
680        assert_eq!(
681            move_record(b"%TORYO"),
682            Result::Ok((
683                &b""[..],
684                MoveRecord {
685                    action: Action::Toryo,
686                    time: None
687                }
688            ))
689        );
690    }
691
692    #[test]
693    fn parse_move_records() {
694        let records = b"\
695+7776FU
696'** 30 -3334FU +2726FU
697-3334FU
698T5
699'*jouseki
700+2726FU
701";
702        assert_eq!(
703            move_records(records),
704            Result::Ok((
705                &b""[..],
706                vec![
707                    MoveRecord {
708                        action: Action::Move(
709                            Color::Black,
710                            Square::new(7, 7),
711                            Square::new(7, 6),
712                            PieceType::Pawn
713                        ),
714                        time: None,
715                    },
716                    MoveRecord {
717                        action: Action::Move(
718                            Color::White,
719                            Square::new(3, 3),
720                            Square::new(3, 4),
721                            PieceType::Pawn
722                        ),
723                        time: Some(Duration::from_secs(5)),
724                    },
725                    MoveRecord {
726                        action: Action::Move(
727                            Color::Black,
728                            Square::new(2, 7),
729                            Square::new(2, 6),
730                            PieceType::Pawn
731                        ),
732                        time: None,
733                    },
734                ]
735            ))
736        );
737    }
738
739    #[test]
740    fn parse_game_record() {
741        let csa = "\
742'----------棋譜ファイルの例\"example.csa\"-----------------
743'バージョン
744V2.2
745'対局者名
746N+NAKAHARA
747N-YONENAGA
748'棋譜情報
749'棋戦名
750$EVENT:13th World Computer Shogi Championship
751'対局場所
752$SITE:KAZUSA ARC
753'開始日時
754$START_TIME:2003/05/03 10:30:00
755'終了日時
756$END_TIME:2003/05/03 11:11:05
757'持ち時間:25分、切れ負け
758$TIME_LIMIT:00:25+00
759'戦型:矢倉
760$OPENING:YAGURA
761'平手の局面
762P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
763P2 * -HI *  *  *  *  * -KA * 
764P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
765P4 *  *  *  *  *  *  *  *  * 
766P5 *  *  *  *  *  *  *  *  * 
767P6 *  *  *  *  *  *  *  *  * 
768P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
769P8 * +KA *  *  *  *  * +HI * 
770P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
771'先手番
772+
773'指し手と消費時間
774+2726FU
775T12
776-3334FU
777T6
778%CHUDAN
779'---------------------------------------------------------
780";
781
782        let initial_pos = [
783            [
784                Some((Color::White, PieceType::Lance)),
785                Some((Color::White, PieceType::Knight)),
786                Some((Color::White, PieceType::Silver)),
787                Some((Color::White, PieceType::Gold)),
788                Some((Color::White, PieceType::King)),
789                Some((Color::White, PieceType::Gold)),
790                Some((Color::White, PieceType::Silver)),
791                Some((Color::White, PieceType::Knight)),
792                Some((Color::White, PieceType::Lance)),
793            ],
794            [
795                None,
796                Some((Color::White, PieceType::Rook)),
797                None,
798                None,
799                None,
800                None,
801                None,
802                Some((Color::White, PieceType::Bishop)),
803                None,
804            ],
805            [
806                Some((Color::White, PieceType::Pawn)),
807                Some((Color::White, PieceType::Pawn)),
808                Some((Color::White, PieceType::Pawn)),
809                Some((Color::White, PieceType::Pawn)),
810                Some((Color::White, PieceType::Pawn)),
811                Some((Color::White, PieceType::Pawn)),
812                Some((Color::White, PieceType::Pawn)),
813                Some((Color::White, PieceType::Pawn)),
814                Some((Color::White, PieceType::Pawn)),
815            ],
816            [None, None, None, None, None, None, None, None, None],
817            [None, None, None, None, None, None, None, None, None],
818            [None, None, None, None, None, None, None, None, None],
819            [
820                Some((Color::Black, PieceType::Pawn)),
821                Some((Color::Black, PieceType::Pawn)),
822                Some((Color::Black, PieceType::Pawn)),
823                Some((Color::Black, PieceType::Pawn)),
824                Some((Color::Black, PieceType::Pawn)),
825                Some((Color::Black, PieceType::Pawn)),
826                Some((Color::Black, PieceType::Pawn)),
827                Some((Color::Black, PieceType::Pawn)),
828                Some((Color::Black, PieceType::Pawn)),
829            ],
830            [
831                None,
832                Some((Color::Black, PieceType::Bishop)),
833                None,
834                None,
835                None,
836                None,
837                None,
838                Some((Color::Black, PieceType::Rook)),
839                None,
840            ],
841            [
842                Some((Color::Black, PieceType::Lance)),
843                Some((Color::Black, PieceType::Knight)),
844                Some((Color::Black, PieceType::Silver)),
845                Some((Color::Black, PieceType::Gold)),
846                Some((Color::Black, PieceType::King)),
847                Some((Color::Black, PieceType::Gold)),
848                Some((Color::Black, PieceType::Silver)),
849                Some((Color::Black, PieceType::Knight)),
850                Some((Color::Black, PieceType::Lance)),
851            ],
852        ];
853
854        assert_eq!(
855            game_record(csa.as_bytes()),
856            Result::Ok((
857                &b""[..],
858                GameRecord {
859                    black_player: Some("NAKAHARA".to_string()),
860                    white_player: Some("YONENAGA".to_string()),
861                    event: Some("13th World Computer Shogi Championship".to_string()),
862                    site: Some("KAZUSA ARC".to_string()),
863                    start_time: Some(Time {
864                        date: NativeDate::from_calendar_date(2003, time::Month::May, 3).unwrap(),
865                        time: Some(NativeTime::from_hms(10, 30, 0).unwrap())
866                    }),
867                    end_time: Some(Time {
868                        date: NativeDate::from_calendar_date(2003, time::Month::May, 3).unwrap(),
869                        time: Some(NativeTime::from_hms(11, 11, 5).unwrap())
870                    }),
871                    time_limit: Some(TimeLimit {
872                        main_time: Duration::from_secs(1500),
873                        byoyomi: Duration::from_secs(0)
874                    }),
875                    opening: Some("YAGURA".to_string()),
876                    start_pos: Position {
877                        drop_pieces: vec![],
878                        bulk: Some(initial_pos),
879                        add_pieces: vec![],
880                        side_to_move: Color::Black,
881                    },
882                    moves: vec![
883                        MoveRecord {
884                            action: Action::Move(
885                                Color::Black,
886                                Square::new(2, 7),
887                                Square::new(2, 6),
888                                PieceType::Pawn
889                            ),
890                            time: Some(Duration::from_secs(12))
891                        },
892                        MoveRecord {
893                            action: Action::Move(
894                                Color::White,
895                                Square::new(3, 3),
896                                Square::new(3, 4),
897                                PieceType::Pawn
898                            ),
899                            time: Some(Duration::from_secs(6))
900                        },
901                        MoveRecord {
902                            action: Action::Chudan,
903                            time: None
904                        }
905                    ],
906                }
907            ))
908        )
909    }
910}