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.
- 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
|