1use crate::{
2 board::scharnagl_by_id,
3 game::{self, Mode, Node, Roster},
4 position::{self, Position},
5};
6
7use super::*;
8
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11 #[error(transparent)]
12 Resolve(#[from] Resolve),
13 #[error("invalid PGN start position {fen}: {error}")]
14 Start { mode: Mode, fen: String, error: position::Error },
15}
16
17#[derive(Debug, thiserror::Error)]
18pub enum Resolve {
24 #[error("SAN error: {0}")]
25 San(#[from] san::Error),
26 #[error("game error: {0}")]
27 Game(#[from] game::Error),
28}
29
30pub type Result<T, E = Error> = core::result::Result<T, E>;
31
32impl From<crate::Game> for Game {
33 fn from(game: crate::Game) -> Self {
34 let mode = game.mode();
35 let freestyle = mode.is_freestyle();
36 let start = game.start();
37 let moves = pgn_moves(&game, game.start_options());
38 let mut tags = pgn_roster(&game.roster, game.outcome);
39 if freestyle {
40 tags.push(Tag::freestyle());
41 }
42 tags.extend(game.tags.into_iter().map(Tag::Other));
43
44 if freestyle || (start.parts() != Position::start().parts()) {
45 pgn_set_start(&mut tags, start.parts());
46 }
47 if freestyle && let Some(id) = scharnagl_by_id(start.board().standard_id()) {
48 tags.push(Tag::Chess960Id(id));
49 }
50
51 Self {
52 tags,
53 start: start.parts(),
54 intro: game.intro.map(Comment),
55 moves,
56 outcome: game.outcome,
57 }
58 }
59}
60
61impl TryFrom<Game> for crate::Game {
62 type Error = Error;
63
64 fn try_from(pgn: Game) -> Result<Self> {
65 let mode = pgn.mode();
66 let position = pgn.start.validate().map_err(|error| Error::Start {
67 mode,
68 fen: pgn.start.fen(),
69 error,
70 })?;
71 Ok(game_from_position(pgn, position, mode)?)
72 }
73}
74
75impl crate::Game {
76 pub fn from_pgn(pgn: Game) -> Result<Self> {
77 pgn.try_into()
78 }
79}
80
81fn game_from_position(
82 pgn: Game,
83 position: Position,
84 mode: Mode,
85) -> core::result::Result<crate::Game, Resolve> {
86 let mut game = crate::Game::new(position, mode);
87 game.roster = game_roster(&pgn.tags);
88 game.tags = game_tags(&pgn.tags);
89 game.intro = pgn.intro.map(Into::into);
90 game.outcome = pgn.outcome;
91
92 game_moves(&mut game, Node::Start, 0, pgn.moves)?;
93
94 Ok(game)
95}
96
97fn game_moves(
98 game: &mut crate::Game,
99 mut previous: Node,
100 mut ply: usize,
101 moves: Vec<Move>,
102) -> core::result::Result<(), Resolve> {
103 for pgn_move in moves {
104 let mut options = game.options_mut(previous).expect("previous play exists");
105 let play = pgn_move.san.resolve(options.as_ref().legal())?;
106 let mut play = options.push(play)?;
107 let slot = play.slot();
108 play.meta.comment = pgn_move.comment.map(Into::into);
109 for annotation in pgn_move.annotations {
110 match annotation {
111 Annotation::Nag(nag) => play.meta.nags.push(nag),
112 Annotation::Command(command) => play.meta.commands.push(command),
113 }
114 }
115
116 for variation in pgn_move.variations {
117 game_variation(game, previous, ply, variation)?;
118 }
119
120 previous = Node::Play(slot);
121 ply += 1;
122 }
123
124 Ok(())
125}
126
127fn game_variation(
128 game: &mut crate::Game,
129 previous: Node,
130 ply: usize,
131 variation: Variation,
132) -> core::result::Result<(), Resolve> {
133 let Some((first, rest)) = variation.moves.split_first() else {
134 return Ok(());
135 };
136
137 let mut options = game.options_mut(previous).expect("previous play exists");
138 let play = first.san.resolve(options.as_ref().legal())?;
139 let mut play = options.push(play)?;
140 let slot = play.slot();
141 play.meta.intro = variation.intro.map(Into::into);
142 play.meta.outro = variation.outro.map(Into::into);
143 play.meta.comment = first.comment.clone().map(Into::into);
144 for annotation in &first.annotations {
145 match annotation {
146 Annotation::Nag(nag) => play.meta.nags.push(nag.clone()),
147 Annotation::Command(command) => play.meta.commands.push(command.clone()),
148 }
149 }
150
151 for variation in &first.variations {
152 game_variation(game, previous, ply, variation.clone())?;
153 }
154 game_moves(game, Node::Play(slot), ply + 1, rest.to_vec())
155}
156
157fn game_roster(tags: &[Tag]) -> Roster {
164 let mut roster = Roster::default();
165
166 for tag in tags {
167 match tag {
168 Tag::Event(value) => roster.event = roster_text(value, "?"),
169 Tag::Site(value) => roster.site = roster_text(value, "?"),
170 Tag::Date(value) => roster.date = roster_text(value, "????.??.??"),
171 Tag::Round(value) => roster.round = roster_text(value, "?"),
172 Tag::White(value) => roster.white = roster_text(value, "?"),
173 Tag::Black(value) => roster.black = roster_text(value, "?"),
174 Tag::Outcome(_)
175 | Tag::Fen(_)
176 | Tag::SetUp(_)
177 | Tag::Variant(_)
178 | Tag::Chess960Id(_)
179 | Tag::Other(_) => {}
180 }
181 }
182
183 roster
184}
185
186fn roster_text(value: &str, default: &str) -> Option<game::Text> {
187 let value = game::Text::new(value)?;
188 (value.as_ref() != default).then_some(value)
189}
190
191fn game_tags(tags: &[Tag]) -> Vec<game::Tag> {
192 tags.iter()
193 .filter_map(|tag| match tag {
194 Tag::Other(tag) => Some(tag.clone()),
195 _ => None,
196 })
197 .collect()
198}
199
200fn pgn_roster(roster: &Roster, outcome: game::Outcome) -> Vec<Tag> {
201 let mut tags = Vec::new();
202 if let Some(value) = &roster.event {
203 tags.push(Tag::Event(value.to_string()));
204 }
205 if let Some(value) = &roster.site {
206 tags.push(Tag::Site(value.to_string()));
207 }
208 if let Some(value) = &roster.date {
209 tags.push(Tag::Date(value.to_string()));
210 }
211 if let Some(value) = &roster.round {
212 tags.push(Tag::Round(value.to_string()));
213 }
214 if let Some(value) = &roster.white {
215 tags.push(Tag::White(value.to_string()));
216 }
217 if let Some(value) = &roster.black {
218 tags.push(Tag::Black(value.to_string()));
219 }
220 tags.push(Tag::Outcome(outcome));
221 tags
222}
223
224fn pgn_set_start(tags: &mut Vec<Tag>, position: position::Parts) {
225 tags.push(Tag::SetUp(true));
226 tags.push(Tag::Fen(position));
227}
228
229fn pgn_moves<'g>(game: &'g crate::Game, options: game::OptionsRef<'g>) -> Vec<Move> {
230 let mut moves = Vec::new();
231 let mut options = options;
232
233 while let Some((play, variations)) = options.split_first() {
234 moves.push(pgn_move(game, &play, variations));
235 options = play.options();
236 }
237
238 moves
239}
240
241fn pgn_move(
242 game: &crate::Game,
243 play: &game::Play,
244 variations: game::OptionsRef<'_>,
245) -> Move {
246 Move {
247 san: san::San::from((play.play(), play.short(), play.check())),
248 comment: play.meta.comment.clone().map(Comment),
249 annotations: pgn_annotations(&play.meta),
250 variations: variations.iter().map(|play| pgn_variation(game, play.slot())).collect(),
251 }
252}
253
254fn pgn_variation(game: &crate::Game, slot: Slot) -> Variation {
255 let play = game.play(slot).expect("option must reference an existing play");
256 Variation {
257 intro: play.meta.intro.clone().map(Comment),
258 moves: pgn_moves_from(game, &play),
259 outro: play.meta.outro.clone().map(Comment),
260 }
261}
262
263fn pgn_moves_from<'g>(game: &'g crate::Game, play: &game::PlayRef<'g>) -> Vec<Move> {
264 let mut moves = vec![pgn_move_without_variations(play)];
265 moves.extend(pgn_moves(game, play.options()));
266 moves
267}
268
269fn pgn_move_without_variations(play: &game::Play) -> Move {
270 Move {
271 san: san::San::from((play.play(), play.short(), play.check())),
272 comment: play.meta.comment.clone().map(Comment),
273 annotations: pgn_annotations(&play.meta),
274 variations: Vec::new(),
275 }
276}
277
278fn pgn_annotations(meta: &game::Meta) -> Vec<Annotation> {
279 meta.nags
280 .iter()
281 .cloned()
282 .map(Annotation::Nag)
283 .chain(meta.commands.iter().cloned().map(Annotation::Command))
284 .collect()
285}