1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use crate::{CLASSIC_RULESET, Columns, Piece, PieceType, PlayerColor, Position, Turn};
use super::San;
impl San {
/// Converts a `Turn` into its corresponding SAN representation.
/// - `turn` - The turn object that will be converted
/// - `current_position` - The position in which the turn is player
/// - `result` - The resulting san string
#[must_use]
pub fn export(turn: &Turn, current_position: &Position) -> String {
let mut san_turn = String::new();
let mut is_capture = current_position
.get_field_occupation(&turn.target)
.is_some();
let from_field = current_position.get_field_occupation(&turn.current);
let Some(moving_piece) = from_field else {
todo!() // TODO: Handle illegal move
};
if moving_piece.get_type() == PieceType::Pawn {
let check_field: i8 = {
match current_position.get_active_color() {
PlayerColor::Black => (turn.target.get_row() as i8) + 1,
PlayerColor::White => (turn.target.get_row() as i8) - 1,
}
};
if let Some(field) = current_position.get_en_passant() {
if turn.target.get_column() == field.get_column()
&& check_field == field.get_row() as i8
{
is_capture = true;
}
}
if is_capture {
san_turn.push((turn.current.get_column() + b'a') as char);
}
Self::to_move(&mut san_turn, turn, current_position, is_capture);
} else {
if PieceType::King == moving_piece.get_type() {
// Is kingside castle
if turn.current.get_column() + 2 == turn.target.get_column() {
return String::from("O-O");
}
if turn.current.get_column() == Columns::COLUMN_E
&& turn.current.get_column() - 2 == turn.target.get_column()
{
return String::from("O-O-O");
}
}
san_turn.push(PieceType::export_piecetype_uppercase(
moving_piece.get_type(),
));
Self::add_field_descriptor(&mut san_turn, turn, current_position);
Self::to_move(&mut san_turn, turn, current_position, is_capture);
}
san_turn
}
/// Checks and adds the needed amount of descriptors for a turn
/// - `base` - The base string which gets data appended to
/// - `turn` - The turn which was played
/// - `current_position` - The position the turn was played in
fn add_field_descriptor(base: &mut String, turn: &Turn, current_position: &Position) {
let column = turn.current.get_column();
let row = turn.current.get_row();
let occupation = current_position.get_field_occupation(&turn.current);
if !Self::is_unique_descriptor(turn, current_position, occupation, None, None) {
if Self::is_unique_descriptor(turn, current_position, occupation, Some(column), None) {
base.push((column + b'a') as char);
} else if Self::is_unique_descriptor(
turn,
current_position,
occupation,
None,
Some(row),
) {
base.push((row + b'1') as char);
} else {
base.push((column + b'a') as char);
base.push((row + b'1') as char);
}
}
}
/// Adds capture `target_field`, promotion and checks to a san string
/// - `base` - The base string of the output
/// - `turn` - The turn which was played
/// - `current_position` - The position the turn was played at
/// - `is_capture` - Indicates whether the current turn is a capturing turn
fn to_move(base: &mut String, turn: &Turn, current_position: &Position, is_capture: bool) {
// Add capture
if is_capture {
base.push('x');
}
// Add target_field
base.push_str(&turn.target.to_string());
// Check if promotion
if let Some(piece) = turn.promotion {
base.push_str(&format!(
"={}",
PieceType::export_piecetype_uppercase(piece)
));
}
// Check if is in check
let copy_position = CLASSIC_RULESET.execute_turn(¤t_position.clone(), turn);
if CLASSIC_RULESET.is_in_check(©_position, copy_position.get_active_color()) {
if CLASSIC_RULESET
.get_possible_turns(©_position)
.is_empty()
{
base.push('#');
} else {
base.push('+');
}
}
}
/// Checks whether the move description is already unique
/// - `checked_turn` - The turn which gets tested for uniqueness
/// - `current_position` - The current position of the game
/// - `piece` - The moving piece
/// - `column` - The column the piece is located at
/// - `row` - The row the piece is located at
/// - `returns` - Whether the move indicator is unique
fn is_unique_descriptor(
checked_turn: &Turn,
current_position: &Position,
occupation: Option<Piece>,
column: Option<u8>,
row: Option<u8>,
) -> bool {
let possible_moves = CLASSIC_RULESET.get_possible_turns(current_position);
let mut counter = 0;
for turn in possible_moves {
if turn.target == checked_turn.target
&& occupation == current_position.get_field_occupation(&turn.current)
{
match column {
Some(column_value) => {
if turn.current.get_column() == column_value {
match row {
Some(row_value) => {
if turn.current.get_row() == row_value {
counter += 1;
}
}
None => counter += 1,
}
}
}
None => match row {
Some(row_value) => {
if turn.current.get_row() == row_value {
counter += 1;
}
}
None => counter += 1,
},
}
}
}
counter == 1
}
}