Class: PGN::Position
- Inherits:
-
Object
- Object
- PGN::Position
- Defined in:
- lib/pgn/position.rb
Overview
Position encapsulates all of the information necessary to completely understand a chess position. It can be turned into a FEN string or perform a move.
Constant Summary collapse
- PLAYERS =
%i[white black].freeze
- CASTLING =
%w[K Q k q].freeze
Instance Attribute Summary collapse
-
#board ⇒ PGN::Board
The board for the position.
-
#castling ⇒ Array<String>
The castling moves that are still available.
-
#en_passant ⇒ String
The en passant square if applicable.
-
#fullmove ⇒ Integer
The number of fullmoves made so far.
-
#halfmove ⇒ Integer
The number of halfmoves since the last pawn move or capture.
-
#player ⇒ Symbol
The player who moves next.
Class Method Summary collapse
-
.start ⇒ PGN::Position
The starting position of a chess game.
Instance Method Summary collapse
-
#eql?(other) ⇒ Boolean
(also: #==)
Positions are equal when their board, side to move, castling rights, and en-passant square match.
- #hash ⇒ Object
-
#initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) ⇒ Position
constructor
A new instance of Position.
- #inspect ⇒ Object
-
#legal_moves ⇒ Array<String>
All legal moves from this position as sorted UCI strings (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed by the native bitboard engine via a FEN round-trip.
-
#move(str) ⇒ PGN::Position
The resulting position.
-
#next_player ⇒ Symbol
The next player to move.
-
#perft(depth) ⇒ Integer
The perft node count at
depthfrom this position, computed by the native bitboard engine via a FEN round-trip. -
#to_fen ⇒ PGN::FEN
A FEN object representing the current position.
-
#zobrist ⇒ Integer
The Zobrist hash of the position.
Constructor Details
#initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) ⇒ Position
Returns a new instance of Position.
62 63 64 65 66 67 68 69 |
# File 'lib/pgn/position.rb', line 62 def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end |
Instance Attribute Details
#board ⇒ PGN::Board
Returns the board for the position.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
#castling ⇒ Array<String>
Returns the castling moves that are still available.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
#en_passant ⇒ String
Returns the en passant square if applicable.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
#fullmove ⇒ Integer
Returns the number of fullmoves made so far.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
#halfmove ⇒ Integer
Returns the number of halfmoves since the last pawn move or capture.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
#player ⇒ Symbol
Returns the player who moves next.
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 |
# File 'lib/pgn/position.rb', line 32 class Position PLAYERS = %i[white black].freeze CASTLING = %w[K Q k q].freeze attr_accessor :board, :player, :castling, :en_passant, :halfmove, :fullmove # @return [PGN::Position] the starting position of a chess game # def self.start PGN::Position.new( PGN::Board.start, PLAYERS.first ) end # @param board [PGN::Board] the board for the position # @param player [Symbol] the player who moves next # @param castling [Array<String>] the castling moves that are still # available # @param en_passant [String, nil] the en passant square if applicable # @param halfmove [Integer] the number of halfmoves since the last pawn # move or capture # @param fullmove [Integer] the number of fullmoves made so far # # @example # PGN::Position.new( # PGN::Board.start, # :white, # ) # def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1) self.board = board self.player = player self.castling = castling self.en_passant = en_passant self.halfmove = halfmove self.fullmove = fullmove end # @param str [String] the move to make in SAN # @return [PGN::Position] the resulting position # # @example # queens_pawn = PGN::Position.start.move("d4") # def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end # @return [Symbol] the next player to move # def next_player player == :white ? :black : :white end # The perft node count at +depth+ from this position, computed by the # native bitboard engine via a FEN round-trip. Requires the compiled # native extension (the shipped gem); raises NameError if it is absent. # # @param depth [Integer] search depth, >= 0 # @return [Integer] # def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end # All legal moves from this position as sorted UCI strings # (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed # by the native bitboard engine via a FEN round-trip. Requires the # compiled native extension; raises NameError if it is absent. # # @return [Array<String>] sorted lexicographically # def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end def inspect "\n#{board.inspect}" end # @return [PGN::FEN] a {PGN::FEN} object representing the current position # def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end # Positions are equal when their board, side to move, castling rights, # and en-passant square match. Halfmove/fullmove counters are ignored # (matching threefold-repetition semantics). def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end alias == eql? def hash zobrist end # The Zobrist hash of the position. Computed lazily on first access and # cached, so the replay hot path (which never asks for the hash) pays # nothing; consumers like threefold-repetition checks pay one full seed. # # @return [Integer] def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end end |
Class Method Details
Instance Method Details
#eql?(other) ⇒ Boolean Also known as: ==
Positions are equal when their board, side to move, castling rights, and en-passant square match. Halfmove/fullmove counters are ignored (matching threefold-repetition semantics).
147 148 149 150 151 152 153 154 |
# File 'lib/pgn/position.rb', line 147 def eql?(other) other.is_a?(PGN::Position) && player == other.player && castling == other.castling && en_passant == other.en_passant && zobrist == other.zobrist && board == other.board end |
#hash ⇒ Object
158 159 160 |
# File 'lib/pgn/position.rb', line 158 def hash zobrist end |
#inspect ⇒ Object
127 128 129 |
# File 'lib/pgn/position.rb', line 127 def inspect "\n#{board.inspect}" end |
#legal_moves ⇒ Array<String>
All legal moves from this position as sorted UCI strings (e.g. "e2e4", "e1g1" for castling, "e7e8q" for promotion), computed by the native bitboard engine via a FEN round-trip. Requires the compiled native extension; raises NameError if it is absent.
123 124 125 |
# File 'lib/pgn/position.rb', line 123 def legal_moves PGN::Bitboard::Engine.new(to_fen.to_s).legal_moves end |
#move(str) ⇒ PGN::Position
Returns the resulting position.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/pgn/position.rb', line 77 def move(str) move = PGN::Move.new(str, player) calculator = PGN::MoveCalculator.new(board, move) restrictions = calculator.castling_restrictions new_castling = restrictions.empty? ? castling : castling - restrictions new_halfmove = calculator.increment_halfmove? ? halfmove + 1 : 0 new_fullmove = calculator.increment_fullmove? ? fullmove + 1 : fullmove no_move = str == '--' PGN::Position.new( no_move ? board : calculator.result_board, next_player, new_castling, calculator.en_passant_square, new_halfmove, new_fullmove ) end |
#next_player ⇒ Symbol
Returns the next player to move.
99 100 101 |
# File 'lib/pgn/position.rb', line 99 def next_player player == :white ? :black : :white end |
#perft(depth) ⇒ Integer
The perft node count at depth from this position, computed by the
native bitboard engine via a FEN round-trip. Requires the compiled
native extension (the shipped gem); raises NameError if it is absent.
110 111 112 113 114 |
# File 'lib/pgn/position.rb', line 110 def perft(depth) raise ArgumentError, 'depth must be a non-negative Integer' unless depth.is_a?(Integer) && depth >= 0 PGN::Bitboard::Engine.new(to_fen.to_s).perft(depth) end |
#to_fen ⇒ PGN::FEN
Returns a FEN object representing the current position.
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/pgn/position.rb', line 133 def to_fen PGN::FEN.from_attributes( board: board, active: player == :white ? 'w' : 'b', castling: castling.join, en_passant: en_passant, halfmove: halfmove.to_s, fullmove: fullmove.to_s ) end |
#zobrist ⇒ Integer
The Zobrist hash of the position. Computed lazily on first access and cached, so the replay hot path (which never asks for the hash) pays nothing; consumers like threefold-repetition checks pay one full seed.
167 168 169 |
# File 'lib/pgn/position.rb', line 167 def zobrist @zobrist ||= Zobrist.seed(board, player, castling, en_passant) end |