Class: PGN::MoveCalculator
- Inherits:
-
Object
- Object
- PGN::MoveCalculator
- Defined in:
- lib/pgn/move_calculator.rb
Overview
MoveCalculator is responsible for computing all of the ways that a specific move changes the current position. This includes which squares on the board need to be updated, new castling restrictions, the en passant square and whether to update fullmove and halfmove counters.
Squares are addressed as 0x88 integer indices (see Board); this
keeps the replay hot path free of [file, rank] coordinate arrays and
square-name string allocations. The public #origin reader still returns
an algebraic square string for API compatibility.
Constant Summary collapse
- SLIDE =
0x88 ray-step offsets for sliding pieces. A step is a single integer add; off-board is
(idx & 0x88) != 0, which also catches file wraparound. { 'b' => [-15, 15, -17, 17], 'r' => [-1, 1, -16, 16], 'q' => [-1, 1, -16, 16, -15, 15, -17, 17] }.freeze
- STEP =
0x88 single-step offsets for knight and king.
{ 'k' => [-1, 1, -16, 16, -15, 15, -17, 17], 'n' => [33, 31, -31, -33, 18, 14, -14, -18] }.freeze
- PAWN_OFFSETS =
Possible pawn origins, expressed as offsets from the destination square (pawn moves are computed backwards from where the pawn landed).
{ 'P' => { capture: [-17, -15], normal: [-16], double: [-32] }, 'p' => { capture: [15, 17], normal: [16], double: [32] } }.freeze
- CASTLING =
The squares to update for each castling move, keyed by 0x88 index.
{ 'Q' => { 0 => nil, 2 => 'K', 3 => 'R', 4 => nil }, 'K' => { 4 => nil, 5 => 'R', 6 => 'K', 7 => nil }, 'q' => { 112 => nil, 114 => 'k', 115 => 'r', 116 => nil }, 'k' => { 116 => nil, 117 => 'r', 118 => 'k', 119 => nil } }.freeze
- A1 =
Corner-square 0x88 indices, used for castling-restriction bookkeeping (a rook leaving or being captured on a corner drops the matching right).
0- H1 =
7- A8 =
112- H8 =
119- ROOK_RESTRICTIONS =
rook-origin (0x88 index) -> castling restriction it drops.
{ A1 => 'Q', H1 => 'K', A8 => 'q', H8 => 'k' }.freeze
- WHITE_CASTLE =
Castling-move characters by side, for the "castling occurs" restriction. Frozen so Array#include? does not allocate per call.
%w[K Q].freeze
- BLACK_CASTLE =
%w[k q].freeze
Instance Attribute Summary collapse
-
#board ⇒ PGN::Board
The current board.
-
#move ⇒ PGN::Move
The current move.
Instance Method Summary collapse
-
#castling_restrictions ⇒ Array<String>
Which castling moves are no longer available.
-
#en_passant_square ⇒ String?
The en passant square if applicable.
-
#increment_fullmove? ⇒ Boolean
Whether to increment the fullmove counter.
-
#increment_halfmove? ⇒ Boolean
Whether to increment the halfmove clock.
-
#initialize(board, move) ⇒ MoveCalculator
constructor
A new instance of MoveCalculator.
-
#origin ⇒ String?
The origin square in algebraic notation, for API compatibility.
-
#result_board ⇒ PGN::Board
The board after the move is made.
Constructor Details
#initialize(board, move) ⇒ MoveCalculator
Returns a new instance of MoveCalculator.
77 78 79 80 81 |
# File 'lib/pgn/move_calculator.rb', line 77 def initialize(board, move) self.board = board self.move = move @origin_idx = compute_origin end |
Instance Attribute Details
#board ⇒ PGN::Board
Returns the current board.
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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/pgn/move_calculator.rb', line 20 class MoveCalculator # 0x88 ray-step offsets for sliding pieces. A step is a single integer # add; off-board is `(idx & 0x88) != 0`, which also catches file wraparound. # SLIDE = { 'b' => [-15, 15, -17, 17], 'r' => [-1, 1, -16, 16], 'q' => [-1, 1, -16, 16, -15, 15, -17, 17] }.freeze # 0x88 single-step offsets for knight and king. # STEP = { 'k' => [-1, 1, -16, 16, -15, 15, -17, 17], 'n' => [33, 31, -31, -33, 18, 14, -14, -18] }.freeze # Possible pawn origins, expressed as offsets from the destination square # (pawn moves are computed backwards from where the pawn landed). # PAWN_OFFSETS = { 'P' => { capture: [-17, -15], normal: [-16], double: [-32] }, 'p' => { capture: [15, 17], normal: [16], double: [32] } }.freeze # The squares to update for each castling move, keyed by 0x88 index. # CASTLING = { 'Q' => { 0 => nil, 2 => 'K', 3 => 'R', 4 => nil }, 'K' => { 4 => nil, 5 => 'R', 6 => 'K', 7 => nil }, 'q' => { 112 => nil, 114 => 'k', 115 => 'r', 116 => nil }, 'k' => { 116 => nil, 117 => 'r', 118 => 'k', 119 => nil } }.freeze # Corner-square 0x88 indices, used for castling-restriction bookkeeping # (a rook leaving or being captured on a corner drops the matching right). # A1 = 0 H1 = 7 A8 = 112 H8 = 119 # rook-origin (0x88 index) -> castling restriction it drops. # ROOK_RESTRICTIONS = { A1 => 'Q', H1 => 'K', A8 => 'q', H8 => 'k' }.freeze # Castling-move characters by side, for the "castling occurs" restriction. # Frozen so {Array#include?} does not allocate per call. # WHITE_CASTLE = %w[K Q].freeze BLACK_CASTLE = %w[k q].freeze attr_accessor :board, :move # @param board [PGN::Board] the current board # @param move [PGN::Move] the current move # def initialize(board, move) self.board = board self.move = move @origin_idx = compute_origin end # @return [String, nil] the origin square in algebraic notation, for API # compatibility. Internally the calculator works with the 0x88 index # (see {#origin_idx}); this reader materialises the string on demand. # def origin return nil if @origin_idx.nil? board.position_for([@origin_idx & 0x0F, @origin_idx >> 4]) end # @return [PGN::Board] the board after the move is made # def result_board new_board = board.dup new_board.apply!(changes) new_board end # @return [Array<String>] which castling moves are no longer available # def castling_restrictions restrict = [] case move.piece when 'K' restrict << 'K' << 'Q' when 'k' restrict << 'k' << 'q' when 'R', 'r' restrict << ROOK_RESTRICTIONS[@origin_idx] end # when castling occurs if WHITE_CASTLE.include?(move.castle) restrict << 'K' << 'Q' elsif BLACK_CASTLE.include?(move.castle) restrict << 'k' << 'q' end # when a rook is taken dest = dest_idx restrict << 'Q' if dest == A1 restrict << 'q' if dest == A8 restrict << 'K' if dest == H1 restrict << 'k' if dest == H8 restrict.empty? ? restrict : restrict.compact.uniq end # @return [Boolean] whether to increment the halfmove clock # def increment_halfmove? !(move.capture || move.pawn?) end # @return [Boolean] whether to increment the fullmove counter # def increment_fullmove? move.black? end # @return [String, nil] the en passant square if applicable # def en_passant_square return nil if move.castle return nil unless move.pawn? && ((origin_rank - dest_rank).abs == 2) Board::INDEX_TO_FILE[origin_file] + (move.white? ? '3' : '6') end private # The integer-indexed changes to apply to the board. Keys are 0x88 # indices, so no square-name strings are allocated on the hot path. # def changes changes = {} changes.merge!(CASTLING[move.castle]) if move.castle changes[@origin_idx] = nil changes[dest_idx] = move.piece changes[en_passant_capture] = nil changes[dest_idx] = move.promotion if move.promotion changes.reject! { |idx, _| idx.nil? } changes end # Using the current position and move, figure out where the piece # came from (as a 0x88 index). # def compute_origin return nil if move.castle possibilities = case move.piece when 'B', 'R', 'Q', 'b', 'r', 'q' then direction_origins when 'K', 'N', 'k', 'n' then move_origins when 'P', 'p' then pawn_origins else # don't care move, used in variations return nil end possibilities = disambiguate(possibilities) if possibilities.length > 1 possibilities.first end # From the destination square, walk each slider direction until the first # occupied square. If that piece is the moving piece, the square it sits # on is a possible origin. # def direction_origins offsets = SLIDE[move.piece.downcase] dest = dest_idx possibilities = [] offsets.each do |off| square = first_piece(dest, off) possibilities << square if piece_at(square) == move.piece end possibilities end # From the destination square, apply each single-step offset. If the # target square is on the board and holds the moving piece, it is a # possible origin. # def move_origins(offsets = STEP[move.piece.downcase]) dest = dest_idx possibilities = [] offsets.each do |off| target = dest + off next unless (target & 0x88).zero? # rubocop:disable Style/BitwisePredicate possibilities << target if board.at_index(target) == move.piece end possibilities end # Computes the possible pawn origins based on the destination square # and whether or not the move is a capture. # def pawn_origins double = (dest_rank == 3 && move.white?) || (dest_rank == 4 && move.black?) pawn_moves = PAWN_OFFSETS[move.piece] offsets = move.capture ? pawn_moves[:capture] : pawn_moves[:normal] offsets += pawn_moves[:double] if double move_origins(offsets) end def disambiguate(possibilities) possibilities = disambiguate_san(possibilities) possibilities = disambiguate_pawns(possibilities) if possibilities.length > 1 possibilities = disambiguate_discovered_check(possibilities) if possibilities.length > 1 possibilities end # Try to disambiguate based on the standard algebraic notation. # def disambiguate_san(possibilities) return possibilities unless move.disambiguation possibilities.select do |idx| board.position_for([idx & 0x0F, idx >> 4]).match(move.disambiguation) end end # A pawn can't move two spaces if there is a pawn in front of it. A # double-push origin sits on rank 2 (white) or 7 (black); reject those # candidates when more than one pawn could have reached the destination. # def disambiguate_pawns(possibilities) return possibilities unless move.piece.match?(/p/i) && !move.capture possibilities.reject { |idx| (idx >> 4) == 1 || (idx >> 4) == 6 } end # A piece can't move if it would result in a discovered check. # def disambiguate_discovered_check(possibilities) king_idx = king_position SLIDE.each do |attacking_piece, offsets| attacking_piece = attacking_piece.upcase if move.black? offsets.each do |off| square = first_piece(king_idx, off) next unless piece_at(square) == move.piece && possibilities.include?(square) next_square = first_piece(square, off) possibilities.reject! { |p| p == square } if piece_at(next_square) == attacking_piece end end possibilities end # Walks from `idx` in the 0x88 direction `off` until it reaches the edge # of the board or the first occupied square. Returns that square's 0x88 # index, or nil if no piece was encountered before the edge. # def first_piece(idx, off) idx += off while (idx & 0x88).zero? # rubocop:disable Style/BitwisePredicate square = board.at_index(idx) return idx if square idx += off end nil end # Reads the piece at a 0x88 index, returning nil for an off-board (nil) # index. Keeps {#disambiguate_discovered_check} within the configured # complexity limits. # def piece_at(idx) idx && board.at_index(idx) end # If the move is a capture and there is no piece on the destination # square, it must be an en passant capture. The captured pawn sits on the # destination file and the moving pawn's origin rank. # def en_passant_capture return nil if move.castle return nil unless move.capture && board.at_index(dest_idx).nil? (origin_rank * 16) + (dest_idx & 0x0F) end def king_position king = move.white? ? 'K' : 'k' 0.upto(7) do |rank| 0.upto(7) do |file| idx = (rank * 16) + file return idx if board.at_index(idx) == king end end nil end # -- 0x88 index helpers -------------------------------------------------- def dest_idx @dest_idx ||= move.destination && board.index_of(move.destination) end def origin_file @origin_idx & 0x0F end def origin_rank @origin_idx >> 4 end def dest_rank dest_idx >> 4 end end |
#move ⇒ PGN::Move
Returns the current move.
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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/pgn/move_calculator.rb', line 20 class MoveCalculator # 0x88 ray-step offsets for sliding pieces. A step is a single integer # add; off-board is `(idx & 0x88) != 0`, which also catches file wraparound. # SLIDE = { 'b' => [-15, 15, -17, 17], 'r' => [-1, 1, -16, 16], 'q' => [-1, 1, -16, 16, -15, 15, -17, 17] }.freeze # 0x88 single-step offsets for knight and king. # STEP = { 'k' => [-1, 1, -16, 16, -15, 15, -17, 17], 'n' => [33, 31, -31, -33, 18, 14, -14, -18] }.freeze # Possible pawn origins, expressed as offsets from the destination square # (pawn moves are computed backwards from where the pawn landed). # PAWN_OFFSETS = { 'P' => { capture: [-17, -15], normal: [-16], double: [-32] }, 'p' => { capture: [15, 17], normal: [16], double: [32] } }.freeze # The squares to update for each castling move, keyed by 0x88 index. # CASTLING = { 'Q' => { 0 => nil, 2 => 'K', 3 => 'R', 4 => nil }, 'K' => { 4 => nil, 5 => 'R', 6 => 'K', 7 => nil }, 'q' => { 112 => nil, 114 => 'k', 115 => 'r', 116 => nil }, 'k' => { 116 => nil, 117 => 'r', 118 => 'k', 119 => nil } }.freeze # Corner-square 0x88 indices, used for castling-restriction bookkeeping # (a rook leaving or being captured on a corner drops the matching right). # A1 = 0 H1 = 7 A8 = 112 H8 = 119 # rook-origin (0x88 index) -> castling restriction it drops. # ROOK_RESTRICTIONS = { A1 => 'Q', H1 => 'K', A8 => 'q', H8 => 'k' }.freeze # Castling-move characters by side, for the "castling occurs" restriction. # Frozen so {Array#include?} does not allocate per call. # WHITE_CASTLE = %w[K Q].freeze BLACK_CASTLE = %w[k q].freeze attr_accessor :board, :move # @param board [PGN::Board] the current board # @param move [PGN::Move] the current move # def initialize(board, move) self.board = board self.move = move @origin_idx = compute_origin end # @return [String, nil] the origin square in algebraic notation, for API # compatibility. Internally the calculator works with the 0x88 index # (see {#origin_idx}); this reader materialises the string on demand. # def origin return nil if @origin_idx.nil? board.position_for([@origin_idx & 0x0F, @origin_idx >> 4]) end # @return [PGN::Board] the board after the move is made # def result_board new_board = board.dup new_board.apply!(changes) new_board end # @return [Array<String>] which castling moves are no longer available # def castling_restrictions restrict = [] case move.piece when 'K' restrict << 'K' << 'Q' when 'k' restrict << 'k' << 'q' when 'R', 'r' restrict << ROOK_RESTRICTIONS[@origin_idx] end # when castling occurs if WHITE_CASTLE.include?(move.castle) restrict << 'K' << 'Q' elsif BLACK_CASTLE.include?(move.castle) restrict << 'k' << 'q' end # when a rook is taken dest = dest_idx restrict << 'Q' if dest == A1 restrict << 'q' if dest == A8 restrict << 'K' if dest == H1 restrict << 'k' if dest == H8 restrict.empty? ? restrict : restrict.compact.uniq end # @return [Boolean] whether to increment the halfmove clock # def increment_halfmove? !(move.capture || move.pawn?) end # @return [Boolean] whether to increment the fullmove counter # def increment_fullmove? move.black? end # @return [String, nil] the en passant square if applicable # def en_passant_square return nil if move.castle return nil unless move.pawn? && ((origin_rank - dest_rank).abs == 2) Board::INDEX_TO_FILE[origin_file] + (move.white? ? '3' : '6') end private # The integer-indexed changes to apply to the board. Keys are 0x88 # indices, so no square-name strings are allocated on the hot path. # def changes changes = {} changes.merge!(CASTLING[move.castle]) if move.castle changes[@origin_idx] = nil changes[dest_idx] = move.piece changes[en_passant_capture] = nil changes[dest_idx] = move.promotion if move.promotion changes.reject! { |idx, _| idx.nil? } changes end # Using the current position and move, figure out where the piece # came from (as a 0x88 index). # def compute_origin return nil if move.castle possibilities = case move.piece when 'B', 'R', 'Q', 'b', 'r', 'q' then direction_origins when 'K', 'N', 'k', 'n' then move_origins when 'P', 'p' then pawn_origins else # don't care move, used in variations return nil end possibilities = disambiguate(possibilities) if possibilities.length > 1 possibilities.first end # From the destination square, walk each slider direction until the first # occupied square. If that piece is the moving piece, the square it sits # on is a possible origin. # def direction_origins offsets = SLIDE[move.piece.downcase] dest = dest_idx possibilities = [] offsets.each do |off| square = first_piece(dest, off) possibilities << square if piece_at(square) == move.piece end possibilities end # From the destination square, apply each single-step offset. If the # target square is on the board and holds the moving piece, it is a # possible origin. # def move_origins(offsets = STEP[move.piece.downcase]) dest = dest_idx possibilities = [] offsets.each do |off| target = dest + off next unless (target & 0x88).zero? # rubocop:disable Style/BitwisePredicate possibilities << target if board.at_index(target) == move.piece end possibilities end # Computes the possible pawn origins based on the destination square # and whether or not the move is a capture. # def pawn_origins double = (dest_rank == 3 && move.white?) || (dest_rank == 4 && move.black?) pawn_moves = PAWN_OFFSETS[move.piece] offsets = move.capture ? pawn_moves[:capture] : pawn_moves[:normal] offsets += pawn_moves[:double] if double move_origins(offsets) end def disambiguate(possibilities) possibilities = disambiguate_san(possibilities) possibilities = disambiguate_pawns(possibilities) if possibilities.length > 1 possibilities = disambiguate_discovered_check(possibilities) if possibilities.length > 1 possibilities end # Try to disambiguate based on the standard algebraic notation. # def disambiguate_san(possibilities) return possibilities unless move.disambiguation possibilities.select do |idx| board.position_for([idx & 0x0F, idx >> 4]).match(move.disambiguation) end end # A pawn can't move two spaces if there is a pawn in front of it. A # double-push origin sits on rank 2 (white) or 7 (black); reject those # candidates when more than one pawn could have reached the destination. # def disambiguate_pawns(possibilities) return possibilities unless move.piece.match?(/p/i) && !move.capture possibilities.reject { |idx| (idx >> 4) == 1 || (idx >> 4) == 6 } end # A piece can't move if it would result in a discovered check. # def disambiguate_discovered_check(possibilities) king_idx = king_position SLIDE.each do |attacking_piece, offsets| attacking_piece = attacking_piece.upcase if move.black? offsets.each do |off| square = first_piece(king_idx, off) next unless piece_at(square) == move.piece && possibilities.include?(square) next_square = first_piece(square, off) possibilities.reject! { |p| p == square } if piece_at(next_square) == attacking_piece end end possibilities end # Walks from `idx` in the 0x88 direction `off` until it reaches the edge # of the board or the first occupied square. Returns that square's 0x88 # index, or nil if no piece was encountered before the edge. # def first_piece(idx, off) idx += off while (idx & 0x88).zero? # rubocop:disable Style/BitwisePredicate square = board.at_index(idx) return idx if square idx += off end nil end # Reads the piece at a 0x88 index, returning nil for an off-board (nil) # index. Keeps {#disambiguate_discovered_check} within the configured # complexity limits. # def piece_at(idx) idx && board.at_index(idx) end # If the move is a capture and there is no piece on the destination # square, it must be an en passant capture. The captured pawn sits on the # destination file and the moving pawn's origin rank. # def en_passant_capture return nil if move.castle return nil unless move.capture && board.at_index(dest_idx).nil? (origin_rank * 16) + (dest_idx & 0x0F) end def king_position king = move.white? ? 'K' : 'k' 0.upto(7) do |rank| 0.upto(7) do |file| idx = (rank * 16) + file return idx if board.at_index(idx) == king end end nil end # -- 0x88 index helpers -------------------------------------------------- def dest_idx @dest_idx ||= move.destination && board.index_of(move.destination) end def origin_file @origin_idx & 0x0F end def origin_rank @origin_idx >> 4 end def dest_rank dest_idx >> 4 end end |
Instance Method Details
#castling_restrictions ⇒ Array<String>
Returns which castling moves are no longer available.
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 |
# File 'lib/pgn/move_calculator.rb', line 104 def castling_restrictions restrict = [] case move.piece when 'K' restrict << 'K' << 'Q' when 'k' restrict << 'k' << 'q' when 'R', 'r' restrict << ROOK_RESTRICTIONS[@origin_idx] end # when castling occurs if WHITE_CASTLE.include?(move.castle) restrict << 'K' << 'Q' elsif BLACK_CASTLE.include?(move.castle) restrict << 'k' << 'q' end # when a rook is taken dest = dest_idx restrict << 'Q' if dest == A1 restrict << 'q' if dest == A8 restrict << 'K' if dest == H1 restrict << 'k' if dest == H8 restrict.empty? ? restrict : restrict.compact.uniq end |
#en_passant_square ⇒ String?
Returns the en passant square if applicable.
147 148 149 150 151 152 |
# File 'lib/pgn/move_calculator.rb', line 147 def en_passant_square return nil if move.castle return nil unless move.pawn? && ((origin_rank - dest_rank).abs == 2) Board::INDEX_TO_FILE[origin_file] + (move.white? ? '3' : '6') end |
#increment_fullmove? ⇒ Boolean
Returns whether to increment the fullmove counter.
141 142 143 |
# File 'lib/pgn/move_calculator.rb', line 141 def increment_fullmove? move.black? end |
#increment_halfmove? ⇒ Boolean
Returns whether to increment the halfmove clock.
135 136 137 |
# File 'lib/pgn/move_calculator.rb', line 135 def increment_halfmove? !(move.capture || move.pawn?) end |
#origin ⇒ String?
Returns the origin square in algebraic notation, for API compatibility. Internally the calculator works with the 0x88 index (see #origin_idx); this reader materialises the string on demand.
87 88 89 90 91 |
# File 'lib/pgn/move_calculator.rb', line 87 def origin return nil if @origin_idx.nil? board.position_for([@origin_idx & 0x0F, @origin_idx >> 4]) end |
#result_board ⇒ PGN::Board
Returns the board after the move is made.
95 96 97 98 99 100 |
# File 'lib/pgn/move_calculator.rb', line 95 def result_board new_board = board.dup new_board.apply!(changes) new_board end |