@@ -3,6 +3,7 @@ use std::str::FromStr;
33use lopdf:: dictionary;
44use lopdf:: { Document , Object , Stream } ;
55use lopdf:: content:: { Content , Operation } ;
6+ use chrono;
67use chess:: { Board , ChessMove , Color , Piece , Square } ;
78
89use crate :: { config, PuzzleTab , lang} ;
@@ -343,3 +344,89 @@ fn gen_diagram_operations(index: usize, puzzle: &config::Puzzle, start_x:i32, st
343344 ops
344345}
345346
347+ pub fn to_pgn ( puzzles : & Vec < config:: Puzzle > , lang : & lang:: Language , path : String ) {
348+ let mut pgn_content = String :: new ( ) ;
349+
350+ for ( puzzle_index, puzzle) in puzzles. iter ( ) . enumerate ( ) {
351+ // Start with a board from the FEN
352+ let mut board = Board :: from_str ( & puzzle. fen ) . unwrap ( ) ;
353+
354+ // Add PGN headers
355+ pgn_content. push_str ( & format ! ( "[Event \" Chess Puzzle\" ]\n " ) ) ;
356+ pgn_content. push_str ( & format ! ( "[Site \" https://lichess.org/training/{}\" ]\n " , puzzle. puzzle_id) ) ;
357+ pgn_content. push_str ( & format ! ( "[Date \" {}\" ]\n " , chrono:: Local :: now( ) . format( "%Y.%m.%d" ) ) ) ;
358+ pgn_content. push_str ( & format ! ( "[White \" {}\" ]\n " , if board. side_to_move( ) == Color :: White { "Player" } else { "Opponent" } ) ) ;
359+ pgn_content. push_str ( & format ! ( "[Black \" {}\" ]\n " , if board. side_to_move( ) == Color :: Black { "Player" } else { "Opponent" } ) ) ;
360+ pgn_content. push_str ( & format ! ( "[Result \" *\" ]\n " ) ) ;
361+ pgn_content. push_str ( & format ! ( "[GameID \" {}\" ]\n " , puzzle. game_url) ) ;
362+ pgn_content. push_str ( & format ! ( "[FEN \" {}\" ]\n " , puzzle. fen) ) ;
363+ pgn_content. push_str ( & format ! ( "[SetUp \" 1\" ]\n " ) ) ;
364+ if !puzzle. opening . is_empty ( ) {
365+ pgn_content. push_str ( & format ! ( "[Opening \" {}\" ]\n " , puzzle. opening) ) ;
366+ }
367+ // Add puzzle details
368+ pgn_content. push_str ( & format ! ( "[PuzzleRating \" {}\" ]\n " , puzzle. rating) ) ;
369+ pgn_content. push_str ( & format ! ( "[PuzzleRatingDeviation \" {}\" ]\n " , puzzle. rating_deviation) ) ;
370+ pgn_content. push_str ( & format ! ( "[PuzzlePopularity \" {}\" ]\n " , puzzle. popularity) ) ;
371+ pgn_content. push_str ( & format ! ( "[PuzzleNbPlays \" {}\" ]\n " , puzzle. nb_plays) ) ;
372+ pgn_content. push_str ( & format ! ( "[PuzzleThemes \" {}\" ]\n " , puzzle. themes) ) ;
373+
374+ // Start the move list
375+ let puzzle_moves: Vec < & str > = puzzle. moves . split_whitespace ( ) . collect ( ) ;
376+ let mut move_number = 1 ;
377+ let mut is_white_to_move = board. side_to_move ( ) == Color :: White ;
378+
379+ // Process the first move (opponent's move that sets up the puzzle)
380+ let first_move = puzzle_moves[ 0 ] ;
381+ let movement = ChessMove :: new (
382+ Square :: from_str ( & String :: from ( & first_move[ ..2 ] ) ) . unwrap ( ) ,
383+ Square :: from_str ( & String :: from ( & first_move[ 2 ..4 ] ) ) . unwrap ( ) ,
384+ PuzzleTab :: check_promotion ( first_move)
385+ ) ;
386+
387+ let san_move = config:: coord_to_san ( & board, String :: from ( first_move) , lang) . unwrap ( ) ;
388+
389+ if is_white_to_move {
390+ pgn_content. push_str ( & format ! ( "{}. {}" , move_number, san_move) ) ;
391+ } else {
392+ pgn_content. push_str ( & format ! ( "{}... {}" , move_number, san_move) ) ;
393+ move_number += 1 ;
394+ }
395+
396+ // Apply the move to the board
397+ board = board. make_move_new ( movement) ;
398+ is_white_to_move = !is_white_to_move;
399+
400+ // Process the rest of the moves (the actual puzzle solution)
401+ for chess_move in puzzle_moves. iter ( ) . skip ( 1 ) {
402+ if is_white_to_move {
403+ pgn_content. push_str ( & format ! ( " {}. " , move_number) ) ;
404+ } else {
405+ pgn_content. push_str ( " " ) ;
406+ }
407+
408+ let san_move = config:: coord_to_san ( & board, String :: from ( * chess_move) , lang) . unwrap ( ) ;
409+ pgn_content. push_str ( & san_move) ;
410+
411+ // Apply the move to the board
412+ let movement = ChessMove :: new (
413+ Square :: from_str ( & String :: from ( & chess_move[ ..2 ] ) ) . unwrap ( ) ,
414+ Square :: from_str ( & String :: from ( & chess_move[ 2 ..4 ] ) ) . unwrap ( ) ,
415+ PuzzleTab :: check_promotion ( chess_move)
416+ ) ;
417+ board = board. make_move_new ( movement) ;
418+
419+ if !is_white_to_move {
420+ move_number += 1 ;
421+ }
422+ is_white_to_move = !is_white_to_move;
423+ }
424+
425+ // End the game with a result
426+ pgn_content. push_str ( " *\n \n " ) ;
427+ }
428+
429+ // Write to file
430+ std:: fs:: write ( path, pgn_content) . expect ( "Unable to write PGN file" ) ;
431+ }
432+
0 commit comments