Fix the pawn unit tests
This commit is contained in:
		
							parent
							
								
									63c03fb879
								
							
						
					
					
						commit
						2a6b098cb8
					
				
					 4 changed files with 112 additions and 45 deletions
				
			
		| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
// Eryn Wells <eryn@erynwells.me>
 | 
					// Eryn Wells <eryn@erynwells.me>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::Color;
 | 
				
			||||||
use std::fmt;
 | 
					use std::fmt;
 | 
				
			||||||
use std::str::FromStr;
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -168,6 +169,16 @@ impl Rank {
 | 
				
			||||||
    /// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
 | 
					    /// assert_eq!(Rank::PAWN_STARTING_RANKS[Color::Black as usize], Rank::SEVEN);
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN];
 | 
					    pub const PAWN_STARTING_RANKS: [Rank; 2] = [Rank::TWO, Rank::SEVEN];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const PAWN_DOUBLE_PUSH_TARGET_RANKS: [Rank; 2] = [Rank::FOUR, Rank::FIVE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn is_pawn_starting_rank(&self, color: Color) -> bool {
 | 
				
			||||||
 | 
					        self == &Self::PAWN_STARTING_RANKS[color as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn is_pawn_double_push_target_rank(&self, color: Color) -> bool {
 | 
				
			||||||
 | 
					        self == &Self::PAWN_DOUBLE_PUSH_TARGET_RANKS[color as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[rustfmt::skip]
 | 
					#[rustfmt::skip]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
use chessfriend_core::{Rank, Square};
 | 
					use chessfriend_core::{Rank, Square};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// En passant information.
 | 
					/// En passant information.
 | 
				
			||||||
#[derive(Clone, Copy, Debug)]
 | 
					#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 | 
				
			||||||
pub struct EnPassant {
 | 
					pub struct EnPassant {
 | 
				
			||||||
    target: Square,
 | 
					    target: Square,
 | 
				
			||||||
    capture: Square,
 | 
					    capture: Square,
 | 
				
			||||||
| 
						 | 
					@ -39,10 +39,12 @@ impl EnPassant {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The square the capturing piece will move to.
 | 
				
			||||||
    pub fn target_square(&self) -> Square {
 | 
					    pub fn target_square(&self) -> Square {
 | 
				
			||||||
        self.target
 | 
					        self.target
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The square on which the captured pawn sits.
 | 
				
			||||||
    pub fn capture_square(&self) -> Square {
 | 
					    pub fn capture_square(&self) -> Square {
 | 
				
			||||||
        self.capture
 | 
					        self.capture
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chessfriend_bitboard::BitBoard;
 | 
					use chessfriend_bitboard::BitBoard;
 | 
				
			||||||
use chessfriend_core::{PlacedPiece, Square};
 | 
					use chessfriend_core::{PlacedPiece, Square};
 | 
				
			||||||
use chessfriend_moves::{Builder as MoveBuilder, Castle, Move};
 | 
					use chessfriend_moves::{Builder as MoveBuilder, Castle, EnPassant, Move};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A set of bitboards defining the moves for a single piece on the board.
 | 
					/// A set of bitboards defining the moves for a single piece on the board.
 | 
				
			||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
 | 
					#[derive(Clone, Debug, Default, Eq, PartialEq)]
 | 
				
			||||||
| 
						 | 
					@ -11,12 +11,18 @@ struct BitBoardSet {
 | 
				
			||||||
    captures: BitBoard,
 | 
					    captures: BitBoard,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, Eq, PartialEq)]
 | 
				
			||||||
 | 
					pub(crate) enum Special {
 | 
				
			||||||
 | 
					    Pawn { en_passant: EnPassant },
 | 
				
			||||||
 | 
					    King { castles: u8 },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A set of moves for a single piece on the board.
 | 
					/// A set of moves for a single piece on the board.
 | 
				
			||||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
					#[derive(Clone, Debug, Eq, PartialEq)]
 | 
				
			||||||
pub(crate) struct MoveSet {
 | 
					pub(crate) struct MoveSet {
 | 
				
			||||||
    piece: PlacedPiece,
 | 
					    piece: PlacedPiece,
 | 
				
			||||||
    bitboards: BitBoardSet,
 | 
					    bitboards: BitBoardSet,
 | 
				
			||||||
    special: u8,
 | 
					    special: Option<Special>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MoveSet {
 | 
					impl MoveSet {
 | 
				
			||||||
| 
						 | 
					@ -24,7 +30,7 @@ impl MoveSet {
 | 
				
			||||||
        MoveSet {
 | 
					        MoveSet {
 | 
				
			||||||
            piece,
 | 
					            piece,
 | 
				
			||||||
            bitboards: BitBoardSet::default(),
 | 
					            bitboards: BitBoardSet::default(),
 | 
				
			||||||
            special: 0,
 | 
					            special: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,9 +39,9 @@ impl MoveSet {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn can_castle(&self, castle: Castle) -> bool {
 | 
					    pub(crate) fn can_castle(&self, castle: Castle) -> bool {
 | 
				
			||||||
        match castle {
 | 
					        match self.special {
 | 
				
			||||||
            Castle::KingSide => (self.special & 0b1) != 0,
 | 
					            Some(Special::King { castles }) => (castles & 1 << castle as u8) != 0,
 | 
				
			||||||
            Castle::QueenSide => (self.special & 0b10) != 0,
 | 
					            _ => false,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,49 +56,99 @@ impl MoveSet {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
 | 
					    pub(super) fn kingside_castle(&mut self) -> &mut MoveSet {
 | 
				
			||||||
        self.special |= 0b1;
 | 
					        match self.special {
 | 
				
			||||||
 | 
					            Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::KingSide as u8,
 | 
				
			||||||
 | 
					            _ => {
 | 
				
			||||||
 | 
					                self.special = Some(Special::King {
 | 
				
			||||||
 | 
					                    castles: 1 << Castle::KingSide as u8,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
 | 
					    pub(super) fn queenside_castle(&mut self) -> &mut MoveSet {
 | 
				
			||||||
        self.special |= 0b10;
 | 
					        match self.special {
 | 
				
			||||||
 | 
					            Some(Special::King { ref mut castles }) => *castles |= 1 << Castle::QueenSide as u8,
 | 
				
			||||||
 | 
					            _ => {
 | 
				
			||||||
 | 
					                self.special = Some(Special::King {
 | 
				
			||||||
 | 
					                    castles: 1 << Castle::QueenSide as u8,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Return a BitBoard representing all possible moves.
 | 
					    pub(super) fn en_passant(&mut self, en_passant: EnPassant) -> &mut MoveSet {
 | 
				
			||||||
 | 
					        self.special = Some(Special::Pawn { en_passant });
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// A `BitBoard` representing all possible moves.
 | 
				
			||||||
    pub(super) fn bitboard(&self) -> BitBoard {
 | 
					    pub(super) fn bitboard(&self) -> BitBoard {
 | 
				
			||||||
        self.bitboards.captures | self.bitboards.quiet
 | 
					        self.bitboards.captures | self.bitboards.quiet
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
 | 
					    pub(crate) fn moves(&self) -> impl Iterator<Item = Move> + '_ {
 | 
				
			||||||
        let piece = &self.piece;
 | 
					        let piece = &self.piece;
 | 
				
			||||||
 | 
					        let color = piece.color();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let is_pawn_on_starting_rank =
 | 
				
			||||||
 | 
					            piece.is_pawn() && piece.square().rank().is_pawn_starting_rank(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.bitboards
 | 
					        self.bitboards
 | 
				
			||||||
            .quiet
 | 
					            .quiet
 | 
				
			||||||
            .occupied_squares()
 | 
					            .occupied_squares()
 | 
				
			||||||
            .filter_map(|to_square| MoveBuilder::push(&self.piece).to(to_square).build().ok())
 | 
					            .filter_map(move |to_square| {
 | 
				
			||||||
 | 
					                if is_pawn_on_starting_rank
 | 
				
			||||||
 | 
					                    && to_square.rank().is_pawn_double_push_target_rank(color)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    MoveBuilder::double_push(piece.square().file(), color)
 | 
				
			||||||
 | 
					                        .build()
 | 
				
			||||||
 | 
					                        .ok()
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    MoveBuilder::push(piece).to(to_square).build().ok()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .chain(
 | 
					            .chain(
 | 
				
			||||||
                self.bitboards
 | 
					                self.bitboards
 | 
				
			||||||
                    .captures
 | 
					                    .captures
 | 
				
			||||||
                    .occupied_squares()
 | 
					                    .occupied_squares()
 | 
				
			||||||
                    .filter_map(|to_square| MoveBuilder::push(piece).to(to_square).build().ok()),
 | 
					                    .filter_map(|to_square| {
 | 
				
			||||||
 | 
					                        MoveBuilder::push(piece)
 | 
				
			||||||
 | 
					                            .capturing_on(to_square)
 | 
				
			||||||
 | 
					                            .build()
 | 
				
			||||||
 | 
					                            .ok()
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            .chain(
 | 
					            .chain(self.castle_move(Castle::KingSide))
 | 
				
			||||||
                if (self.special & 0b1) != 0 {
 | 
					            .chain(self.castle_move(Castle::QueenSide))
 | 
				
			||||||
                    let mv = MoveBuilder::castling(Castle::KingSide).build();
 | 
					            .chain(self.en_passant_move())
 | 
				
			||||||
                    Some(mv)
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn castle_move(&self, castle: Castle) -> Option<Move> {
 | 
				
			||||||
 | 
					        match self.special {
 | 
				
			||||||
 | 
					            Some(Special::King { castles }) => {
 | 
				
			||||||
 | 
					                if (castles & 1 << castle as u8) != 0 {
 | 
				
			||||||
 | 
					                    Some(MoveBuilder::castling(castle).build())
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    None
 | 
					                    None
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                .into_iter(),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .chain(
 | 
					 | 
				
			||||||
                if (self.special & 0b10) != 0 {
 | 
					 | 
				
			||||||
                    Some(MoveBuilder::castling(Castle::QueenSide).build())
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    None
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                .into_iter(),
 | 
					            _ => None,
 | 
				
			||||||
            )
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn en_passant_move(&self) -> Option<Move> {
 | 
				
			||||||
 | 
					        match self.special {
 | 
				
			||||||
 | 
					            Some(Special::Pawn { en_passant }) => Some(unsafe {
 | 
				
			||||||
 | 
					                MoveBuilder::push(&self.piece)
 | 
				
			||||||
 | 
					                    .capturing_en_passant_on(en_passant.target_square())
 | 
				
			||||||
 | 
					                    .build_unchecked()
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ use super::{move_generator_declaration, MoveGeneratorInternal, MoveSet};
 | 
				
			||||||
use crate::Position;
 | 
					use crate::Position;
 | 
				
			||||||
use chessfriend_bitboard::BitBoard;
 | 
					use chessfriend_bitboard::BitBoard;
 | 
				
			||||||
use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
 | 
					use chessfriend_core::{Color, PlacedPiece, Rank, Shape, Square};
 | 
				
			||||||
use chessfriend_moves::{Builder as MoveBuilder, Move};
 | 
					use chessfriend_moves::{EnPassant, Move};
 | 
				
			||||||
use std::collections::BTreeMap;
 | 
					use std::collections::BTreeMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
| 
						 | 
					@ -33,27 +33,33 @@ impl MoveGeneratorInternal for PawnMoveGenerator {
 | 
				
			||||||
        let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
 | 
					        let capture_moves = Self::attacks(position, &placed_piece) & capture_mask;
 | 
				
			||||||
        let quiet_moves = Self::pushes(position, &placed_piece) & push_mask;
 | 
					        let quiet_moves = Self::pushes(position, &placed_piece) & push_mask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MoveSet::new(*placed_piece)
 | 
					        let mut move_set = MoveSet::new(*placed_piece)
 | 
				
			||||||
            .quiet_moves(quiet_moves)
 | 
					            .quiet_moves(quiet_moves)
 | 
				
			||||||
            .capture_moves(capture_moves)
 | 
					            .capture_moves(capture_moves);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(en_passant) = Self::en_passant(position, placed_piece) {
 | 
				
			||||||
 | 
					            move_set.en_passant(en_passant);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        move_set
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PawnMoveGenerator {
 | 
					impl PawnMoveGenerator {
 | 
				
			||||||
    pub(super) fn new(
 | 
					    pub(super) fn new(
 | 
				
			||||||
        position: &Position,
 | 
					        position: &Position,
 | 
				
			||||||
        color: Color,
 | 
					        player_to_move: Color,
 | 
				
			||||||
        capture_mask: BitBoard,
 | 
					        capture_mask: BitBoard,
 | 
				
			||||||
        push_mask: BitBoard,
 | 
					        push_mask: BitBoard,
 | 
				
			||||||
    ) -> Self {
 | 
					    ) -> Self {
 | 
				
			||||||
        let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() {
 | 
					        let move_sets = if !capture_mask.is_empty() && !push_mask.is_empty() {
 | 
				
			||||||
            Self::move_sets(position, color, capture_mask, push_mask)
 | 
					            Self::move_sets(position, player_to_move, capture_mask, push_mask)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            std::collections::BTreeMap::new()
 | 
					            std::collections::BTreeMap::new()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            color,
 | 
					            color: player_to_move,
 | 
				
			||||||
            move_sets,
 | 
					            move_sets,
 | 
				
			||||||
            en_passant_captures: Vec::new(),
 | 
					            en_passant_captures: Vec::new(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -115,7 +121,7 @@ impl PawnMoveGenerator {
 | 
				
			||||||
        BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
 | 
					        BitBoard::pawn_attacks(piece.square(), color) & opponent_pieces
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn en_passant_attack(position: &Position, piece: &PlacedPiece) -> Option<Move> {
 | 
					    fn en_passant(position: &Position, piece: &PlacedPiece) -> Option<EnPassant> {
 | 
				
			||||||
        match position.en_passant() {
 | 
					        match position.en_passant() {
 | 
				
			||||||
            Some(en_passant) => {
 | 
					            Some(en_passant) => {
 | 
				
			||||||
                let target_square = en_passant.target_square();
 | 
					                let target_square = en_passant.target_square();
 | 
				
			||||||
| 
						 | 
					@ -128,12 +134,7 @@ impl PawnMoveGenerator {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match position.piece_on_square(en_passant.capture_square()) {
 | 
					                match position.piece_on_square(en_passant.capture_square()) {
 | 
				
			||||||
                    Some(_) => Some(
 | 
					                    Some(_) => Some(en_passant),
 | 
				
			||||||
                        MoveBuilder::push(piece)
 | 
					 | 
				
			||||||
                            .capturing_en_passant_on(target_square)
 | 
					 | 
				
			||||||
                            .build()
 | 
					 | 
				
			||||||
                            .ok()?,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    None => None,
 | 
					                    None => None,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -164,7 +165,7 @@ mod tests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
					        let generated_moves: HashSet<_> = generator.iter().collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(generated_moves, expected_moves);
 | 
					        assert_move_list!(generated_moves, expected_moves, pos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -278,15 +279,12 @@ mod tests {
 | 
				
			||||||
            Black Pawn on E4,
 | 
					            Black Pawn on E4,
 | 
				
			||||||
        ], D3);
 | 
					        ], D3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let generator = PawnMoveGenerator::new(&pos, Color::White, BitBoard::FULL, BitBoard::FULL);
 | 
					        let generator = PawnMoveGenerator::new(&pos, Color::Black, BitBoard::FULL, BitBoard::FULL);
 | 
				
			||||||
        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
					        let generated_moves: HashSet<Move> = generator.iter().collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
 | 
					        let builder = MoveBuilder::push(&piece!(Black Pawn on E4));
 | 
				
			||||||
        let expected_moves = HashSet::from_iter([
 | 
					        let expected_moves = HashSet::from_iter([
 | 
				
			||||||
            builder
 | 
					            builder.capturing_en_passant_on(Square::D3).build()?,
 | 
				
			||||||
                .clone()
 | 
					 | 
				
			||||||
                .capturing_en_passant_on(Square::D3)
 | 
					 | 
				
			||||||
                .build()?,
 | 
					 | 
				
			||||||
            builder.clone().to(Square::E3).build()?,
 | 
					            builder.clone().to(Square::E3).build()?,
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue