sapphire-chess 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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