sapphire-chess 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +21 -0
- data/README.md +24 -0
- data/Rakefile +1 -0
- data/bin/sapphire-chess +5 -0
- data/lib/sapphire-chess/ai.rb +101 -0
- data/lib/sapphire-chess/algebraic_conversion.rb +89 -0
- data/lib/sapphire-chess/board/board_analysis.rb +66 -0
- data/lib/sapphire-chess/board/board_evaluation.rb +25 -0
- data/lib/sapphire-chess/board/board_general.rb +155 -0
- data/lib/sapphire-chess/board/board_provisional_moves.rb +32 -0
- data/lib/sapphire-chess/board/board_renderer.rb +122 -0
- data/lib/sapphire-chess/board.rb +5 -0
- data/lib/sapphire-chess/display.rb +140 -0
- data/lib/sapphire-chess/engine.rb +153 -0
- data/lib/sapphire-chess/human_input_validation.rb +123 -0
- data/lib/sapphire-chess/movement_rules/castling_board_control.rb +37 -0
- data/lib/sapphire-chess/movement_rules/castling_piece_control.rb +9 -0
- data/lib/sapphire-chess/movement_rules/castling_rights.rb +79 -0
- data/lib/sapphire-chess/movement_rules/en_passant_board_control.rb +20 -0
- data/lib/sapphire-chess/movement_rules/en_passant_piece_control.rb +36 -0
- data/lib/sapphire-chess/movement_rules/move_slide_pattern.rb +23 -0
- data/lib/sapphire-chess/movement_rules/move_step_pattern.rb +17 -0
- data/lib/sapphire-chess/movement_rules/pawn_movement_and_promotion.rb +71 -0
- data/lib/sapphire-chess/movement_rules.rb +7 -0
- data/lib/sapphire-chess/pieces/bishop.rb +56 -0
- data/lib/sapphire-chess/pieces/empty_square.rb +13 -0
- data/lib/sapphire-chess/pieces/king.rb +77 -0
- data/lib/sapphire-chess/pieces/knight.rb +57 -0
- data/lib/sapphire-chess/pieces/pawn.rb +82 -0
- data/lib/sapphire-chess/pieces/piece.rb +77 -0
- data/lib/sapphire-chess/pieces/queen.rb +44 -0
- data/lib/sapphire-chess/pieces/rook.rb +62 -0
- data/lib/sapphire-chess/pieces.rb +8 -0
- data/lib/sapphire-chess/player.rb +43 -0
- data/lib/sapphire-chess/version.rb +3 -0
- data/lib/sapphire-chess.rb +9 -0
- data/sapphire-chess.gemspec +29 -0
- 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,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,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
|