sapphire-chess 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +45 -0
  4. data/LICENSE +21 -0
  5. data/README.md +24 -0
  6. data/Rakefile +1 -0
  7. data/bin/sapphire-chess +5 -0
  8. data/lib/sapphire-chess/ai.rb +101 -0
  9. data/lib/sapphire-chess/algebraic_conversion.rb +89 -0
  10. data/lib/sapphire-chess/board/board_analysis.rb +66 -0
  11. data/lib/sapphire-chess/board/board_evaluation.rb +25 -0
  12. data/lib/sapphire-chess/board/board_general.rb +155 -0
  13. data/lib/sapphire-chess/board/board_provisional_moves.rb +32 -0
  14. data/lib/sapphire-chess/board/board_renderer.rb +122 -0
  15. data/lib/sapphire-chess/board.rb +5 -0
  16. data/lib/sapphire-chess/display.rb +140 -0
  17. data/lib/sapphire-chess/engine.rb +153 -0
  18. data/lib/sapphire-chess/human_input_validation.rb +123 -0
  19. data/lib/sapphire-chess/movement_rules/castling_board_control.rb +37 -0
  20. data/lib/sapphire-chess/movement_rules/castling_piece_control.rb +9 -0
  21. data/lib/sapphire-chess/movement_rules/castling_rights.rb +79 -0
  22. data/lib/sapphire-chess/movement_rules/en_passant_board_control.rb +20 -0
  23. data/lib/sapphire-chess/movement_rules/en_passant_piece_control.rb +36 -0
  24. data/lib/sapphire-chess/movement_rules/move_slide_pattern.rb +23 -0
  25. data/lib/sapphire-chess/movement_rules/move_step_pattern.rb +17 -0
  26. data/lib/sapphire-chess/movement_rules/pawn_movement_and_promotion.rb +71 -0
  27. data/lib/sapphire-chess/movement_rules.rb +7 -0
  28. data/lib/sapphire-chess/pieces/bishop.rb +56 -0
  29. data/lib/sapphire-chess/pieces/empty_square.rb +13 -0
  30. data/lib/sapphire-chess/pieces/king.rb +77 -0
  31. data/lib/sapphire-chess/pieces/knight.rb +57 -0
  32. data/lib/sapphire-chess/pieces/pawn.rb +82 -0
  33. data/lib/sapphire-chess/pieces/piece.rb +77 -0
  34. data/lib/sapphire-chess/pieces/queen.rb +44 -0
  35. data/lib/sapphire-chess/pieces/rook.rb +62 -0
  36. data/lib/sapphire-chess/pieces.rb +8 -0
  37. data/lib/sapphire-chess/player.rb +43 -0
  38. data/lib/sapphire-chess/version.rb +3 -0
  39. data/lib/sapphire-chess.rb +9 -0
  40. data/sapphire-chess.gemspec +29 -0
  41. metadata +142 -0
@@ -0,0 +1,122 @@
1
+ class BoardRenderer
2
+ LEFT_MARGIN = 4
3
+ RIGHT_MARGIN = 3
4
+
5
+ EMPTY_ROW_0 = '| |'
6
+ EMPTY_ROW_0_WHITE = '|████████|'
7
+ EMPTY_ROW = ' |'
8
+ EMPTY_ROW_WHITE = '████████|'
9
+
10
+ FLOOR_0 = '+--------+'
11
+ FLOOR = '--------+'
12
+
13
+ COLUMN_LETTERS = ('a'..'h').to_a.freeze
14
+ ROW_NUMBERS = [*('1'..'8')].reverse
15
+
16
+ def initialize(board)
17
+ @board = board
18
+ @square_order = board.class::SQUARE_ORDER
19
+ end
20
+
21
+ def render
22
+ print_column_letters
23
+ print_floor
24
+
25
+ print_rows
26
+
27
+ new_line
28
+ print_column_letters
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :board, :square_order
34
+
35
+ def print_column_letters
36
+ COLUMN_LETTERS.each { |letter| print " #{letter}" }
37
+ new_line(2)
38
+ end
39
+
40
+ def print_floor
41
+ puts ' ' * LEFT_MARGIN + FLOOR_0 + FLOOR * (square_order - 1)
42
+ end
43
+
44
+ def print_rows
45
+ square_order.times do |number|
46
+ print_row(number)
47
+ print ROW_NUMBERS[number]
48
+ print_piece_row(number)
49
+ puts (' ' * RIGHT_MARGIN) + ROW_NUMBERS[number]
50
+ print_row(number)
51
+ print_floor
52
+ end
53
+ end
54
+
55
+ def print_row(number)
56
+ puts number.even? ? white_starting_row : black_starting_row
57
+ end
58
+
59
+ def white_starting_row
60
+ ' ' * LEFT_MARGIN +
61
+ EMPTY_ROW_0_WHITE +
62
+ (EMPTY_ROW + EMPTY_ROW_WHITE) * 3 +
63
+ EMPTY_ROW
64
+ end
65
+
66
+ def black_starting_row
67
+ ' ' * LEFT_MARGIN +
68
+ EMPTY_ROW_0 +
69
+ (EMPTY_ROW_WHITE + EMPTY_ROW) * 3 +
70
+ EMPTY_ROW_WHITE
71
+ end
72
+
73
+ def print_piece_row(row)
74
+ square_order.times do |column|
75
+ square = [row, column]
76
+
77
+ if white_square?(square) then print_white_square(square, column)
78
+ else
79
+ print_black_square(square, column)
80
+ end
81
+ end
82
+ end
83
+
84
+ def white_square?(square)
85
+ row = square.first
86
+ column = square.last
87
+
88
+ row.even? && column.even? || row.odd? && column.odd?
89
+ end
90
+
91
+ def print_white_square(square, column)
92
+ print(
93
+ if board[square].is_a?(EmptySquare) && column.zero?
94
+ " |███#{board[square].white}███|"
95
+ elsif column.zero?
96
+ " |██ #{board[square]} ██|"
97
+ elsif board[square].is_a?(EmptySquare)
98
+ "███#{board[square].white}███|"
99
+ else
100
+ "██ #{board[square]} ██|"
101
+ end
102
+ )
103
+ end
104
+
105
+ def print_black_square(square, column)
106
+ print(
107
+ if board[square].is_a?(EmptySquare) && column.zero?
108
+ " | #{board[square]} |"
109
+ elsif column.zero?
110
+ " | #{board[square]} |"
111
+ elsif board[square].is_a?(EmptySquare)
112
+ " #{board[square]} |"
113
+ else
114
+ " #{board[square]} |"
115
+ end
116
+ )
117
+ end
118
+
119
+ def new_line(lines = 1)
120
+ lines.times { puts '' }
121
+ end
122
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'board/board_general'
2
+ require_relative 'board/board_renderer'
3
+ require_relative 'board/board_analysis'
4
+ require_relative 'board/board_evaluation'
5
+ require_relative 'board/board_provisional_moves'
@@ -0,0 +1,140 @@
1
+ module Display
2
+ def clear_screen
3
+ system 'clear'
4
+ end
5
+
6
+ def new_line(lines = 1)
7
+ puts '' * lines
8
+ end
9
+
10
+ def display_welcome
11
+ clear_screen
12
+
13
+ print '♟ ♞ ♝ ♜ ♛ ♚ '
14
+ print Paint[" 💎 Welcome to Sapphire Chess #{SapphireChess::VERSION}! 💎 ", :green]
15
+ puts Paint[' ♚ ♛ ♜ ♝ ♞ ♟', :blue]
16
+ new_line
17
+ end
18
+
19
+ def display_game_modes
20
+ puts "Please, select the game mode: (1/2)\n\n"\
21
+ "1) You against the machine.\n\n"\
22
+ "2) You against a friend using this same computer.\n\n"\
23
+ "3) Enjoy watching the computer playing against itself.\n\n"
24
+ end
25
+
26
+ def display_difficulty_settings
27
+ puts "Please, enter the game difficulty:\n"\
28
+ "[i.e.: \"1\", \"e\" or \"easy\" to select Easy]\n\n"\
29
+ "1) Easy\n2) Medium\n3) Hard\n\n"\
30
+ "This setting determines how many turns the computer can think ahead.\n"\
31
+ 'Warning: the "hard" setting is very hard!'
32
+ end
33
+
34
+ def display_move_message
35
+ puts "What piece do you want to move?\n"\
36
+ "[Use algebraic notation, i.e.: 'a2a4']\n"\
37
+ "[To castle, 'castle (k for king side, q for queen side), "\
38
+ "i.e: 'castle k']\n\n"
39
+ end
40
+
41
+ def display_last_moves
42
+ new_line
43
+ return if turn_number < 2
44
+
45
+ print Paint['Last moves: ', :green]
46
+ display_move(:white)
47
+ display_move(:black)
48
+ new_line
49
+ end
50
+
51
+ def display_move(color)
52
+ player = color == :white ? white_player : black_player
53
+
54
+ if color == :white
55
+ print Paint["White #{player.last_move}"]
56
+ print Paint[' | ', :green]
57
+ else
58
+ puts Paint["Black #{player.last_move}", :blue]
59
+ end
60
+ end
61
+
62
+ def display_turn_number
63
+ print Paint[" Turn #{turn_number.to_i} ", nil, :green]
64
+ end
65
+
66
+ def display_player_turn
67
+ puts Paint[
68
+ "It's #{current_player.color}'s turn!",
69
+ nil,
70
+ current_player.color,
71
+ :bright
72
+ ]
73
+ end
74
+
75
+ def display_graphic_score
76
+ %i[black white].each do |color|
77
+ message =
78
+ case color
79
+ when :white then Paint['White player score', :white, :underline]
80
+ else
81
+ Paint['Black player score', :blue, :underline]
82
+ end
83
+ print "#{message}: "
84
+ display_material_score(color)
85
+ new_line(2)
86
+ end
87
+ end
88
+
89
+ def display_material_score(color)
90
+ [Pawn, Knight, Bishop, Rook, Queen, King].each do |type|
91
+ symbol = piece_symbol(color, type)
92
+
93
+ score_line = piece_score(type, color, symbol)
94
+
95
+ print score_line unless score_line[-2] == '0'
96
+ end
97
+ end
98
+
99
+ def piece_symbol(color, type)
100
+ white = color == :white
101
+
102
+ if type == Pawn && white then Paint[type::WHITE.first, :white]
103
+ elsif type == Pawn && !white then Paint[type::BLACK.first, :blue]
104
+ elsif type != Pawn && white then Paint[type::WHITE, :white]
105
+ else
106
+ Paint[type::BLACK, :blue]
107
+ end
108
+ end
109
+
110
+ def piece_score(type, color, piece_symbol)
111
+ if type == Queen
112
+ "#{piece_symbol} x "\
113
+ "#{board.count(type, color) + board.promoted_pawns(color)} "
114
+ elsif type == Pawn
115
+ "#{piece_symbol} x "\
116
+ "#{board.count(type, color) - board.promoted_pawns(color)} "
117
+ else
118
+ "#{piece_symbol} x #{board.count(type, color)} "
119
+ end
120
+ end
121
+
122
+ def display_check
123
+ puts Paint['You are in check!', :red, :bright]
124
+ new_line
125
+ end
126
+
127
+ def display_checkmate
128
+ puts Paint['Checkmate!', nil, :red, :bright]
129
+ new_line
130
+ end
131
+
132
+ def display_winner
133
+ puts Paint[
134
+ "#{current_player.color.to_s.capitalize} player wins!",
135
+ nil,
136
+ current_player.color
137
+ ]
138
+ puts 'Thanks for playing Ruby Chess'
139
+ end
140
+ end
@@ -0,0 +1,153 @@
1
+ require_relative 'board/board_general'
2
+ require_relative 'board/board_renderer'
3
+ require_relative 'pieces'
4
+ require_relative 'player'
5
+ require_relative 'display'
6
+ require_relative 'version'
7
+ require_relative 'human_input_validation'
8
+ require_relative 'algebraic_conversion'
9
+
10
+ require 'bundler/setup'
11
+ require 'paint'
12
+
13
+ class Engine
14
+ include Display
15
+ include HumanInputValidation
16
+ include AlgebraicConversion
17
+
18
+ def initialize
19
+ @board = Board.initialize_board
20
+ @renderer = BoardRenderer.new(board)
21
+ @turn_number = 1
22
+ end
23
+
24
+ def play
25
+ display_welcome
26
+ define_players
27
+ @current_player = white_player
28
+ board.add_players!(white_player, black_player)
29
+ if computer_plays?
30
+ set_difficulty
31
+ board.set_game_difficulty
32
+ end
33
+
34
+ main_game_loop
35
+
36
+ end_game
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :white_player, :black_player, :board, :renderer
42
+ attr_accessor :current_player, :turn_number
43
+
44
+ def define_players
45
+ mode = prompt_game_mode
46
+ human_player_color = mode == 1 ? prompt_color : ''
47
+
48
+ @white_player, @black_player = initialize_players(human_player_color, mode: mode)
49
+ end
50
+
51
+ def initialize_players(human_player_color, mode: nil)
52
+ if human_player_color == 'w'
53
+ [Human.new(:white, board), Computer.new(:black, board)]
54
+ elsif human_player_color == 'b'
55
+ [Computer.new(:white, board), Human.new(:black, board)]
56
+ elsif mode == 2
57
+ [Human.new(:white, board), Human.new(:black, board)]
58
+ else
59
+ [Computer.new(:white, board), Computer.new(:black, board)]
60
+ end
61
+ end
62
+
63
+ def computer_plays?
64
+ white_player.is_a?(Computer) || black_player.is_a?(Computer)
65
+ end
66
+
67
+ # See Computer in player.rb, AI#minimax in ai.rb
68
+ def set_difficulty
69
+ difficulty_input = prompt_difficulty
70
+
71
+ [white_player, black_player].each do |player|
72
+ player.depth = difficulty_input if player.is_a?(Computer)
73
+ end
74
+ end
75
+
76
+ def main_game_loop
77
+ until game_over?
78
+ clear_screen
79
+ renderer.render
80
+ display_game_data
81
+ perform_move!(player_move_selection)
82
+ swap_player!
83
+ update_turn_counter
84
+ end
85
+ end
86
+
87
+ def display_game_data
88
+ display_graphic_score
89
+ display_last_moves
90
+ display_turn_number
91
+ display_player_turn
92
+ end
93
+
94
+ def swap_player!
95
+ self.current_player =
96
+ current_player == white_player ? black_player : white_player
97
+ end
98
+
99
+ def update_turn_counter
100
+ self.turn_number += 0.5
101
+ end
102
+
103
+ def player_move_selection
104
+ if current_player.is_a?(Human)
105
+ display_check if board.in_check?(current_player.color)
106
+ prompt_move
107
+ else
108
+ puts "\n🤖 I am thinking... 🤖"
109
+ current_player.select_move
110
+ end
111
+ end
112
+
113
+ def perform_move!(move_input)
114
+ piece, target_square = convert_player_input(move_input)
115
+ store_move!(piece, target_square)
116
+
117
+ case piece
118
+ when :castle then board.castle!(target_square, current_player.color, permanent: true)
119
+ else
120
+ board.move_piece!(piece, target_square, permanent: true)
121
+ end
122
+ end
123
+
124
+ def store_move!(piece, target_square)
125
+ current_player.history << [piece, target_square]
126
+
127
+ current_player.last_move = move_to_string(piece, target_square)
128
+ end
129
+
130
+ def move_to_string(piece, target_square)
131
+ if piece == :castle
132
+ "Castle, #{target_square} side"
133
+ elsif board[target_square].is_a?(Piece)
134
+ "#{board[piece].class} #{convert_to_algebraic(piece)} "\
135
+ "to #{board[target_square].class} "\
136
+ "#{convert_to_algebraic(target_square)}"
137
+ else
138
+ "#{board[piece].class} #{convert_to_algebraic(piece)} "\
139
+ "to #{convert_to_algebraic(target_square)}"
140
+ end
141
+ end
142
+
143
+ def end_game
144
+ clear_screen
145
+ renderer.render
146
+ swap_player!
147
+ display_winner
148
+ end
149
+
150
+ def game_over?
151
+ board.checkmate?(current_player.color) || board.no_king?(current_player.color)
152
+ end
153
+ end
@@ -0,0 +1,123 @@
1
+ module HumanInputValidation
2
+ def prompt_color
3
+ puts 'What color would you like to play? ([W]hite/[B]lack)'
4
+ choice = nil
5
+ loop do
6
+ choice = gets.chomp.strip.downcase
7
+ break if valid_color?(choice)
8
+
9
+ puts 'Please, enter a valid color choice.'
10
+ end
11
+ choice
12
+ end
13
+
14
+ def valid_color?(choice)
15
+ %w[white black w b].include?(choice)
16
+ end
17
+
18
+ def prompt_game_mode
19
+ display_game_modes
20
+ game_mode = nil
21
+ loop do
22
+ game_mode = gets.chomp.strip.to_i
23
+ break if [1, 2, 3].include?(game_mode)
24
+
25
+ puts 'Please, enter a valid game mode.'
26
+ end
27
+ new_line
28
+ game_mode
29
+ end
30
+
31
+ def prompt_difficulty
32
+ display_difficulty_settings
33
+ difficulty_input = nil
34
+ loop do
35
+ difficulty_input = gets.chomp.strip.downcase
36
+ break if valid_difficulty?(difficulty_input)
37
+
38
+ puts 'Please, enter a valid difficulty setting.'
39
+ end
40
+
41
+ case difficulty_input
42
+ when 'easy' then 1
43
+ when 'medium' then 2
44
+ when 'hard' then 3
45
+ else difficulty_input.to_i
46
+ end
47
+ end
48
+
49
+ def valid_difficulty?(difficulty)
50
+ %w[easy medium hard 1 2 3].include?(difficulty)
51
+ end
52
+
53
+ def prompt_move
54
+ display_move_message
55
+
56
+ player_move_input = nil
57
+ loop do
58
+ player_move_input = current_player.select_move
59
+ break if valid_player_input?(player_move_input)
60
+
61
+ puts 'Please, select a valid movement.'
62
+ end
63
+
64
+ player_move_input
65
+ end
66
+
67
+ def prompt_target_square(piece)
68
+ target_square = nil
69
+ puts "Where do you want to move the #{board[piece].class}?"
70
+ loop do
71
+ target_square = current_player.get_move
72
+ break if valid_target_square?(piece, target_square)
73
+
74
+ puts "The #{board[piece].class} selected can't move to that square."
75
+ end
76
+
77
+ target_square
78
+ end
79
+
80
+ def valid_player_input?(player_move_input)
81
+ if double_input?(player_move_input)
82
+ valid_piece_selection?(player_move_input.first) &&
83
+ valid_target_square?(player_move_input.first, player_move_input.last)
84
+ elsif player_move_input.first == :castle
85
+ valid_castling?(player_move_input.last)
86
+ else
87
+ valid_piece_selection?(player_move_input)
88
+ end
89
+ end
90
+
91
+ def double_input?(player_move_input)
92
+ player_move_input.first.is_a?(Array)
93
+ end
94
+
95
+ def single_input?(player_move_input)
96
+ player_move_input.first.is_a?(Integer)
97
+ end
98
+
99
+ def valid_piece_selection?(piece)
100
+ board[piece].is_a?(Piece) &&
101
+ board[piece].color == current_player.color
102
+ end
103
+
104
+ def valid_target_square?(piece, target_square)
105
+ board[piece].available_moves.include?(target_square)
106
+ end
107
+
108
+ def valid_castling?(side)
109
+ current_player.castle_rights?(side)
110
+ end
111
+
112
+ def convert_player_input(player_move_input)
113
+ if single_input?(player_move_input)
114
+ piece = player_move_input
115
+ target_square = prompt_target_square(piece)
116
+ else
117
+ piece = player_move_input.first
118
+ target_square = player_move_input.last
119
+ end
120
+
121
+ [piece, target_square]
122
+ end
123
+ end
@@ -0,0 +1,37 @@
1
+ module CastlingBoardControl
2
+ # (See Castling, movement.rb::CastlingPieceControl, Rook, King)
3
+ def mark_moved_piece!(piece)
4
+ return unless self[piece].is_a?(Rook) || self[piece].is_a?(King)
5
+
6
+ self[piece].mark!
7
+ end
8
+
9
+ def castle!(side, color, permanent: false)
10
+ castling_squares =
11
+ if color == :white && side == :king then [[7, 4], [7, 6], [7, 7], [7, 5]]
12
+ elsif color == :white && side == :queen then [[7, 4], [7, 2], [7, 0], [7, 3]]
13
+ elsif color == :black && side == :king then [[0, 4], [0, 6], [0, 7], [0, 5]]
14
+ else
15
+ [[0, 4], [0, 2], [0, 0], [0, 3]]
16
+ end
17
+
18
+ king_start, king_target, rook_start, rook_target = castling_squares
19
+
20
+ move_piece!(king_start, king_target, permanent: permanent)
21
+ move_piece!(rook_start, rook_target, permanent: permanent)
22
+ end
23
+
24
+ def uncastle!(side, color)
25
+ castling_squares =
26
+ if color == :white && side == :king then [[7, 6], [7, 4], [7, 5], [7, 7]]
27
+ elsif color == :white && side == :queen then [[7, 2], [7, 4], [7, 3], [7, 0]]
28
+ elsif color == :black && side == :king then [[0, 6], [0, 4], [0, 5], [0, 7]]
29
+ else
30
+ [[0, 2], [0, 4], [0, 3], [0, 0]]
31
+ end
32
+ king_start, king_target, rook_start, rook_target = castling_squares
33
+
34
+ move_piece!(king_start, king_target, permanent: false)
35
+ move_piece!(rook_start, rook_target, permanent: false)
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module CastlingPieceControl
2
+ def moved?
3
+ @moved
4
+ end
5
+
6
+ def mark!
7
+ @moved = true
8
+ end
9
+ end
@@ -0,0 +1,79 @@
1
+ module CastlingRights
2
+ def castle_rights?(side)
3
+ !king_and_rook_moved?(side) &&
4
+ castling_line_free?(side) &&
5
+ !board.in_check?(color) &&
6
+ !results_in_check?(side) &&
7
+ !king_crosses_attack_line?(side)
8
+ end
9
+
10
+ private
11
+
12
+ def king_and_rook_moved?(side)
13
+ case color
14
+ when :white
15
+ king = [7, 4]
16
+ rook = side == :king ? [7, 7] : [7, 0]
17
+ else
18
+ king = [0, 4]
19
+ rook = side == :king ? [0, 7] : [0, 0]
20
+ end
21
+
22
+ return true unless board[king].is_a?(King) && board[rook].is_a?(Rook)
23
+
24
+ board[king].moved? && board[rook].moved?
25
+ end
26
+
27
+ def castling_line_free?(side)
28
+ if color == :white && side == :king then f1_to_g1_free?
29
+ elsif color == :white && side == :queen then b1_to_d1_free?
30
+ elsif color == :black && side == :king then f8_to_g8_free?
31
+ else
32
+ b8_to_d8_free?
33
+ end
34
+ end
35
+
36
+ def f1_to_g1_free?
37
+ board.empty_square?([7, 5]) &&
38
+ board.empty_square?([7, 6])
39
+ end
40
+
41
+ def b1_to_d1_free?
42
+ board.empty_square?([7, 1]) &&
43
+ board.empty_square?([7, 2]) &&
44
+ board.empty_square?([7, 3])
45
+ end
46
+
47
+ def f8_to_g8_free?
48
+ board.empty_square?([0, 5]) &&
49
+ board.empty_square?([0, 6])
50
+ end
51
+
52
+ def b8_to_d8_free?
53
+ board.empty_square?([0, 1]) &&
54
+ board.empty_square?([0, 2]) &&
55
+ board.empty_square?([0, 3])
56
+ end
57
+
58
+ def results_in_check?(side)
59
+ return true if king_and_rook_moved?(side)
60
+ board.castle!(side, color)
61
+ in_check = board.in_check?(color)
62
+ board.uncastle!(side, color)
63
+
64
+ in_check
65
+ end
66
+
67
+ def king_crosses_attack_line?(side)
68
+ hot_square = case color
69
+ when :white then side == :king ? [7, 5] : [7, 3]
70
+ else side == :king ? [0, 5] : [0, 3]
71
+ end
72
+
73
+ board.enemy_pieces(color).each do |piece|
74
+ return true if piece.available_moves.include?(hot_square)
75
+ end
76
+
77
+ false
78
+ end
79
+ end
@@ -0,0 +1,20 @@
1
+ module EnPassantBoardControl
2
+ def capture_passed_pawn!(target_square)
3
+ captured_pawn = passed_pawn(target_square)
4
+
5
+ self[captured_pawn] = EmptySquare.instance
6
+ end
7
+
8
+ def was_en_passant?(piece, target_square)
9
+ captured_pawn = passed_pawn(target_square)
10
+
11
+ self[target_square].is_a?(Pawn) &&
12
+ self[target_square].pawn_to_pass(piece).include?(captured_pawn)
13
+ end
14
+
15
+ def passed_pawn(target_square)
16
+ direction = self[target_square].color == :white ? 1 : -1
17
+
18
+ [target_square.first + direction, target_square.last]
19
+ end
20
+ end