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 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#[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}