chess_engine 0.0.7 → 0.0.8
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 +4 -4
- data/lib/chess_engine/board.rb +95 -0
- data/lib/chess_engine/cli.rb +106 -0
- data/lib/chess_engine/game.rb +148 -0
- data/lib/chess_engine/input.rb +19 -0
- data/lib/chess_engine/move.rb +35 -0
- data/lib/chess_engine/piece.rb +97 -0
- data/lib/chess_engine/validator.rb +87 -0
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96485fa28f530a602da170422a2b08b757a674bef18cc89d995f620b91c9d4f0
|
4
|
+
data.tar.gz: b9d5e4ba5d7e632bc9db2906667deee9c4b807e76371eff33022c3a9c88f5fc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2eca066e4f4ad8c4afd56f82a580a40e7568f187ca4e68a1c914fab1ae3d16c48bf75e28fe8a9b8992bcb44db51618aa016440112be590b12b81f7e7cbe75b7d
|
7
|
+
data.tar.gz: 8f20a87cd9d933ce5cd5b945937d7d3d208edf476633d4e0236f63ed25c262630161f01f5581d227ea8c893eb50bd8f308debe78021213fca11a8d0e0082d912
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative "piece"
|
2
|
+
require "colorize"
|
3
|
+
|
4
|
+
module ChessEngine
|
5
|
+
class Board
|
6
|
+
def initialize
|
7
|
+
@board = Array.new(8) { Array.new(8) { nil } }
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_default
|
11
|
+
[[:white, 0, 1], [:black, 7, 6]].each do |color, row1, row2|
|
12
|
+
["Rook", "Knight", "Elephant", "Queen", "King", "Elephant", "Knight", "Rook"].each.with_index do |class_name, column|
|
13
|
+
self[column, row1] = Module.const_get("ChessEngine::#{class_name}").new(color)
|
14
|
+
end
|
15
|
+
|
16
|
+
0.upto(7) do |column|
|
17
|
+
self[column, row2] = Pawn.new(color)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](column, row)
|
23
|
+
@board[column][row]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(column, row, piece)
|
27
|
+
@board[column][row] = piece
|
28
|
+
end
|
29
|
+
|
30
|
+
def at(coordinates)
|
31
|
+
return nil unless self.exists_at?(coordinates)
|
32
|
+
@board[coordinates[0]][coordinates[1]]
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_at(coordinates, piece)
|
36
|
+
@board[coordinates[0]][coordinates[1]] = piece
|
37
|
+
end
|
38
|
+
|
39
|
+
def exists_at?(coordinates)
|
40
|
+
coordinates.all? { |c| c.between?(0, 7) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
string = ""
|
45
|
+
colors = [[:default, :light_white].cycle, [:light_white, :default].cycle].cycle
|
46
|
+
|
47
|
+
7.downto(0) do |row|
|
48
|
+
string += "#{row + 1} "
|
49
|
+
colors_cycle = colors.next
|
50
|
+
|
51
|
+
0.upto(7) do |column|
|
52
|
+
piece = self[column, row]
|
53
|
+
string += piece.nil? ? " " : piece.symbol
|
54
|
+
string += " "
|
55
|
+
string[-2..-1] = string[-2..-1].colorize(background: colors_cycle.next)
|
56
|
+
end
|
57
|
+
string += "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
string += " a b c d e f g h"
|
61
|
+
string
|
62
|
+
end
|
63
|
+
|
64
|
+
def move_piece(from, to)
|
65
|
+
piece = self.at(from)
|
66
|
+
self.set_at(from, nil)
|
67
|
+
self.set_at(to, piece)
|
68
|
+
end
|
69
|
+
|
70
|
+
def pieces(color)
|
71
|
+
@board.flatten.compact.select { |piece| piece.color == color }
|
72
|
+
end
|
73
|
+
|
74
|
+
def Board.coordinates_list
|
75
|
+
list = []
|
76
|
+
(0..7).each do |x|
|
77
|
+
(0..7).each { |y| list << [x, y]}
|
78
|
+
end
|
79
|
+
list
|
80
|
+
end
|
81
|
+
|
82
|
+
def king_coords(color)
|
83
|
+
Board.coordinates_list.find do |coord|
|
84
|
+
at(coord) && at(coord).king? && at(coord).color == color
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def piece_coordinates(color)
|
89
|
+
Board.coordinates_list.select do |coord|
|
90
|
+
piece = at(coord)
|
91
|
+
!piece.nil? && piece.color == color
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require_relative "game"
|
2
|
+
require_relative "input"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
|
6
|
+
module ChessEngine
|
7
|
+
class NoGamesError < StandardError; end
|
8
|
+
|
9
|
+
class CLI
|
10
|
+
include Input
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
begin
|
14
|
+
mode = get_input("\nChoose the game mode:\n1. New game\n2. Continue\nEnter your choice (1 or 2): ", /^[12]$/)
|
15
|
+
@game = mode == "1" ? Game.new : choose_game
|
16
|
+
rescue NoGamesError => e
|
17
|
+
puts "#{e.message}. Try again"
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
play
|
21
|
+
save if save?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def play
|
27
|
+
until @game.over?
|
28
|
+
puts "\n#{@game.name}\n#{@game.draw}"
|
29
|
+
declare_check if @game.check?
|
30
|
+
begin
|
31
|
+
promotion if @game.needs_promotion?
|
32
|
+
print "#{@game.current_color.to_s.capitalize}'s move (e. g. \"e2e4\" or \"exit\" or \"000\"/\"00\" (for castling)): "
|
33
|
+
move = gets.chomp.gsub(/\s+/, " ")
|
34
|
+
case move
|
35
|
+
when /^[oOоО0]{2}$/ #matches cyrillic letters
|
36
|
+
@game.castling(:short)
|
37
|
+
when /^[oOоО0]{3}$/
|
38
|
+
@game.castling(:long)
|
39
|
+
when /^[a-h][1-8][a-h][1-8]$/i
|
40
|
+
@game.move(move)
|
41
|
+
when /^exit$/i
|
42
|
+
return
|
43
|
+
else
|
44
|
+
raise InvalidMove, "Incorrect input"
|
45
|
+
end
|
46
|
+
rescue InvalidMove => e
|
47
|
+
puts "#{e.message}. Try again"
|
48
|
+
retry
|
49
|
+
end
|
50
|
+
end
|
51
|
+
game_over
|
52
|
+
end
|
53
|
+
|
54
|
+
def promotion
|
55
|
+
pieces = ["Queen", "Rook", "Knight", "Elephant"]
|
56
|
+
choice = get_input(
|
57
|
+
"Pawn promotion. Choose the new piece:\n1. Queen\n2. Rook\n3. Knight\n4. Elephant",
|
58
|
+
/^[1-4]$/, "Input must be a single digit"
|
59
|
+
)
|
60
|
+
@game.promotion(pieces[choice - 1])
|
61
|
+
end
|
62
|
+
|
63
|
+
def declare_check
|
64
|
+
puts "#{@game.current_color.to_s.capitalize} player is given a check"
|
65
|
+
end
|
66
|
+
|
67
|
+
def declare_checkmate
|
68
|
+
puts "#{@game.current_color.to_s.capitalize} player got mated!"
|
69
|
+
end
|
70
|
+
|
71
|
+
def game_over
|
72
|
+
puts @game.draw
|
73
|
+
if @game.check?
|
74
|
+
declare_checkmate
|
75
|
+
else
|
76
|
+
puts "Stalemate!"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def choose_game
|
81
|
+
saved_names = Dir[File.join(CLI.dirname, "/*.yaml")].map { |filename| File.basename(filename, ".yaml") }
|
82
|
+
raise NoGamesError, "You have no saved games" if saved_names.empty?
|
83
|
+
puts "\nSaved games (#{saved_names.size}):"
|
84
|
+
saved_names.each { |name| puts name }
|
85
|
+
filename = get_input("Enter the name of the game that you want to continue: ",
|
86
|
+
nil, "Such game doesn't exist, try again") { |input| saved_names.include?(input) }
|
87
|
+
return YAML::load(File.read(File.join(CLI.dirname, "#{filename}.yaml")))
|
88
|
+
end
|
89
|
+
|
90
|
+
def save?
|
91
|
+
choice = get_input("Do you want to save the game? (y/n): ", /^[yn]$/i)
|
92
|
+
choice == "y" ? true : false
|
93
|
+
end
|
94
|
+
|
95
|
+
def save
|
96
|
+
@game.name ||= get_input("Enter the game name: ", /[^\/\>\<\|\:\&]+/)
|
97
|
+
game = YAML::dump(@game)
|
98
|
+
Dir.mkdir(CLI.dirname) unless File.exists?(CLI.dirname)
|
99
|
+
File.open(File.join(CLI.dirname, "#{@game.name}.yaml"), "w") { |file| file.write(game) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def CLI.dirname
|
103
|
+
File.join(Dir.home, "./.chess_engine")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require_relative "board"
|
2
|
+
require_relative "validator"
|
3
|
+
require_relative "move"
|
4
|
+
|
5
|
+
module ChessEngine
|
6
|
+
class InvalidMove < StandardError; end
|
7
|
+
|
8
|
+
class Game
|
9
|
+
attr_accessor :name
|
10
|
+
attr_reader :current_color
|
11
|
+
include MoveValidator
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@board = Board.new
|
15
|
+
@board.set_default
|
16
|
+
@current_color = :white
|
17
|
+
@last_piece = nil
|
18
|
+
@name = nil
|
19
|
+
@promotion_coord = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def move(string)
|
23
|
+
from, to = Game.string_to_move(string)
|
24
|
+
piece = @board.at(from)
|
25
|
+
raise InvalidMove, "Game is over" if over?
|
26
|
+
raise InvalidMove, "#{@current_color} player should execute pawn promotion first" if needs_promotion?
|
27
|
+
raise InvalidMove, "Empty square is chosen" if piece.nil?
|
28
|
+
raise InvalidMove, "This is not your piece" unless piece.color == @current_color
|
29
|
+
raise InvalidMove, "Invalid move" unless valid_moves(from).include?(to)
|
30
|
+
move = Move.new(@board, from, to)
|
31
|
+
move.commit
|
32
|
+
if king_attacked?
|
33
|
+
move.rollback
|
34
|
+
raise InvalidMove, "Fatal move"
|
35
|
+
end
|
36
|
+
|
37
|
+
@last_piece = piece
|
38
|
+
piece.moves_count += 1
|
39
|
+
@promotion_coord = to and return if piece.pawn? && [7, 0].include?(to[1])
|
40
|
+
next_player
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](str)
|
44
|
+
letters = ("a".."h").to_a
|
45
|
+
return nil unless /[a-h][1-8]/.match?(str)
|
46
|
+
@board.at([letters.find_index(str[0]), str[1].to_i - 1])
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw
|
50
|
+
@board.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def promotion(class_name)
|
54
|
+
unless ["rook", "knight", "elephant", "queen"].include?(class_name.downcase)
|
55
|
+
raise InvalidMove, "Invalid promotion"
|
56
|
+
end
|
57
|
+
@board.set_at(@promotion_coord, Module.const_get("ChessEngine::#{class_name.capitalize}").new(@current_color))
|
58
|
+
@promotion_coord = nil
|
59
|
+
next_player
|
60
|
+
end
|
61
|
+
|
62
|
+
def castling(length)
|
63
|
+
row = @current_color == :white ? 0 : 7
|
64
|
+
king = @board.at([4, row])
|
65
|
+
if length == :short
|
66
|
+
rook = @board.at([7, row])
|
67
|
+
line = [5, 6]
|
68
|
+
moves = [Move.new(@board, [4, row], [6, row]),
|
69
|
+
Move.new(@board, [7, row], [5, row])]
|
70
|
+
else
|
71
|
+
rook = @board.at([0, row])
|
72
|
+
line = [1, 2, 3]
|
73
|
+
moves = [Move.new(@board, [4, row], [2, row]),
|
74
|
+
Move.new(@board, [0, row], [3, row])]
|
75
|
+
end
|
76
|
+
raise InvalidMove, "Invalid castling" unless
|
77
|
+
king && rook && king.moves_count == 0 && rook.moves_count == 0 &&
|
78
|
+
line.all? { |x| @board.at([x, row]).nil? }
|
79
|
+
|
80
|
+
moves.each { |move| move.commit }
|
81
|
+
if king_attacked?
|
82
|
+
moves.each { |move| move.rollback }
|
83
|
+
raise InvalidMove, "Fatal move"
|
84
|
+
end
|
85
|
+
@last_piece = nil
|
86
|
+
next_player
|
87
|
+
end
|
88
|
+
|
89
|
+
def over?
|
90
|
+
@board.piece_coordinates(@current_color).all? do |coord|
|
91
|
+
safe_moves(coord).empty?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def needs_promotion?
|
96
|
+
!!@promotion_coord
|
97
|
+
end
|
98
|
+
|
99
|
+
def check?
|
100
|
+
king_attacked?
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def king_attacked?
|
106
|
+
king_coords = @board.king_coords(@current_color)
|
107
|
+
[[1, 1], [-1, 1], [-1, -1], [1, -1]].each do |move|
|
108
|
+
next_coords = relative_coords(king_coords, move)
|
109
|
+
piece = @board.at(next_coords)
|
110
|
+
return true if piece && piece.color != @current_color && (piece.pawn? || piece.king?)
|
111
|
+
edge_coords = repeated_move(king_coords, move).last
|
112
|
+
piece = edge_coords.nil? ? nil : @board.at(edge_coords)
|
113
|
+
return true if piece && piece.beats_diagonally?
|
114
|
+
end
|
115
|
+
[[1, 0], [-1, 0], [0, 1], [0, -1]].each do |move|
|
116
|
+
next_coords = relative_coords(king_coords, move)
|
117
|
+
piece = @board.at(next_coords)
|
118
|
+
return true if piece && piece.king?
|
119
|
+
edge_coords = repeated_move(king_coords, move).last
|
120
|
+
piece = edge_coords.nil? ? nil : @board.at(edge_coords)
|
121
|
+
return true if !piece.nil? && piece.beats_straight?
|
122
|
+
end
|
123
|
+
[[1, 2], [2, 1], [1, -2], [-2, 1],
|
124
|
+
[-1, 2], [2, -1], [-1, -2], [-2, -1]].each do |move|
|
125
|
+
coords = relative_coords(king_coords, move)
|
126
|
+
piece = valid_move?(coords) ? @board.at(coords) : nil
|
127
|
+
return true if !piece.nil? && piece.knight?
|
128
|
+
end
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
def Game.string_to_move(string)
|
133
|
+
string = string.gsub(/\s+/, "").downcase
|
134
|
+
raise InvalidMove, "Input must look like \"e2 e4\" or \"a6b5\"" unless /^[a-h][1-8][a-h][1-8]$/.match?(string)
|
135
|
+
letters = ("a".."h").to_a
|
136
|
+
[[letters.find_index(string[0]), string[1].to_i - 1],
|
137
|
+
[letters.find_index(string[2]), string[3].to_i - 1]]
|
138
|
+
end
|
139
|
+
|
140
|
+
def opposite_color
|
141
|
+
@current_color == :white ? :black : :white
|
142
|
+
end
|
143
|
+
|
144
|
+
def next_player
|
145
|
+
@current_color = opposite_color
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Input
|
2
|
+
class Incorrect < StandardError; end
|
3
|
+
|
4
|
+
def get_input(input_message, regex = nil, err_message = "Incorrect input, try again")
|
5
|
+
begin
|
6
|
+
print input_message
|
7
|
+
input = gets.chomp
|
8
|
+
if block_given?
|
9
|
+
raise Input::Incorrect unless yield(input)
|
10
|
+
else
|
11
|
+
raise Input::Incorrect unless regex.match?(input)
|
12
|
+
end
|
13
|
+
rescue
|
14
|
+
puts err_message
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
input
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ChessEngine
|
2
|
+
class Move
|
3
|
+
def initialize(board, from, to)
|
4
|
+
@board = board
|
5
|
+
@from = from
|
6
|
+
@to = to
|
7
|
+
@original_squares = []
|
8
|
+
@original_squares << {coord: from, piece: board.at(from)}
|
9
|
+
@original_squares << {coord: to, piece: board.at(to)}
|
10
|
+
if en_passant?
|
11
|
+
@en_passant_coord = [to[0], from[1]]
|
12
|
+
@original_squares << {coord: @en_passant_coord, piece: board.at(@en_passant_coord)}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit
|
17
|
+
if en_passant?
|
18
|
+
@board.set_at(@en_passant_coord, nil)
|
19
|
+
end
|
20
|
+
@board.move_piece(@from, @to)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rollback
|
24
|
+
@original_squares.each do |square|
|
25
|
+
@board.set_at(square[:coord], square[:piece])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def en_passant?
|
32
|
+
@board.at(@from).pawn? && @from[0] != @to[0] && @board.at(@to).nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module ChessEngine
|
2
|
+
class Piece
|
3
|
+
attr_reader :symbol, :color
|
4
|
+
attr_accessor :moves_count
|
5
|
+
|
6
|
+
def initialize(color)
|
7
|
+
@color = color
|
8
|
+
@moves_count = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"#{self.class}:#{@color}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def beats_diagonally?
|
16
|
+
elephant? || queen?
|
17
|
+
end
|
18
|
+
|
19
|
+
def beats_straight?
|
20
|
+
rook? || queen?
|
21
|
+
end
|
22
|
+
|
23
|
+
["knight", "king", "pawn", "rook", "queen", "elephant"].each do |piece|
|
24
|
+
define_method(:"#{piece}?") do
|
25
|
+
self.class.to_s == "ChessEngine::#{piece.capitalize}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Elephant < Piece
|
31
|
+
def initialize(color)
|
32
|
+
super
|
33
|
+
@symbol = (@color == :black) ? "\u25B2" : "\u25B3"
|
34
|
+
end
|
35
|
+
|
36
|
+
def moves
|
37
|
+
[[1, 1], [1, -1], [-1, 1], [-1, -1]]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class King < Piece
|
42
|
+
def initialize(color)
|
43
|
+
super
|
44
|
+
@symbol = (@color == :black) ? "\u265A" : "\u2654"
|
45
|
+
end
|
46
|
+
|
47
|
+
def moves
|
48
|
+
[[0, 1], [0, -1], [1, 0], [-1, 0],
|
49
|
+
[1, 1], [1, -1], [-1, 1], [-1, -1]]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Knight < Piece
|
54
|
+
def initialize(color)
|
55
|
+
super
|
56
|
+
@symbol = (@color == :black) ? "\u265E" : "\u2658"
|
57
|
+
end
|
58
|
+
|
59
|
+
def moves
|
60
|
+
[[1, 2], [2, 1], [1, -2], [-2, 1],
|
61
|
+
[-1, 2], [2, -1], [-1, -2], [-2, -1]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Pawn < Piece
|
66
|
+
attr_reader :direction
|
67
|
+
|
68
|
+
def initialize(color)
|
69
|
+
super
|
70
|
+
@symbol = (@color == :black) ? "\u265F" : "\u2659"
|
71
|
+
@direction = (@color == :white) ? 1 : -1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Queen < Piece
|
76
|
+
def initialize(color)
|
77
|
+
super
|
78
|
+
@symbol = (@color == :black) ? "\u265B" : "\u2655"
|
79
|
+
end
|
80
|
+
|
81
|
+
def moves
|
82
|
+
[[0, 1], [0, -1], [1, 0], [-1, 0],
|
83
|
+
[1, 1], [1, -1], [-1, 1], [-1, -1]]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Rook < Piece
|
88
|
+
def initialize(color)
|
89
|
+
super
|
90
|
+
@symbol = (@color == :black) ? "\u265C" : "\u2656"
|
91
|
+
end
|
92
|
+
|
93
|
+
def moves
|
94
|
+
[[1, 0], [0, 1], [-1, 0], [0, -1]]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module ChessEngine
|
2
|
+
module MoveValidator
|
3
|
+
def safe_moves(from)
|
4
|
+
valid_moves(from).reject { |move| fatal_move?(from, move) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def valid_moves(from)
|
8
|
+
piece = @board.at(from)
|
9
|
+
if piece.king? || piece.knight?
|
10
|
+
piece.moves.map do |move|
|
11
|
+
to = relative_coords(from, move)
|
12
|
+
to if valid_move?(to)
|
13
|
+
end.compact
|
14
|
+
elsif piece.pawn?
|
15
|
+
pawn_valid_moves(from)
|
16
|
+
else
|
17
|
+
valid_moves_recursive(from)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_moves_recursive(from)
|
22
|
+
piece = @board.at(from)
|
23
|
+
piece.moves.inject([]) do |valid_moves, move|
|
24
|
+
valid_moves.push(*repeated_move(from, move))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def repeated_move(from, move, valid_moves = [])
|
29
|
+
coordinates = relative_coords(from, move)
|
30
|
+
return valid_moves unless valid_move?(coordinates)
|
31
|
+
return valid_moves << coordinates unless @board.at(coordinates).nil?
|
32
|
+
repeated_move(coordinates, move, valid_moves << coordinates)
|
33
|
+
end
|
34
|
+
|
35
|
+
def relative_coords(from, move)
|
36
|
+
[from[0] + move[0], from[1] + move[1]]
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid_move?(coordinates)
|
40
|
+
if @board.exists_at?(coordinates)
|
41
|
+
piece = @board.at(coordinates)
|
42
|
+
return (piece.nil? || piece.color != @current_color)
|
43
|
+
end
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
|
47
|
+
def fatal_move?(from, to)
|
48
|
+
is_fatal = false
|
49
|
+
move = Move.new(@board, from, to)
|
50
|
+
move.commit
|
51
|
+
is_fatal = true if king_attacked?
|
52
|
+
move.rollback
|
53
|
+
is_fatal
|
54
|
+
end
|
55
|
+
|
56
|
+
def pawn_valid_moves(from)
|
57
|
+
pawn = @board.at(from)
|
58
|
+
direction = pawn.direction
|
59
|
+
moves = []
|
60
|
+
next_coords = relative_coords(from, [0, direction])
|
61
|
+
jump_coords = relative_coords(from, [0, direction * 2])
|
62
|
+
take_coords = [relative_coords(from, [1, direction]),
|
63
|
+
relative_coords(from, [-1, direction])]
|
64
|
+
if @board.exists_at?(next_coords) && @board.at(next_coords).nil?
|
65
|
+
moves << next_coords
|
66
|
+
moves << jump_coords unless pawn.moves_count > 0 || @board.at(jump_coords)
|
67
|
+
end
|
68
|
+
take_coords.each do |coords|
|
69
|
+
moves << coords if @board.at(coords) && @board.at(coords).color != pawn.color
|
70
|
+
end
|
71
|
+
en_passant_coords(from) ? moves << en_passant_coords(from) : moves
|
72
|
+
end
|
73
|
+
|
74
|
+
def en_passant_coords(from)
|
75
|
+
pawn = @board.at(from)
|
76
|
+
[1, -1].each do |x|
|
77
|
+
next_coords = [from[0] + x, from[1]]
|
78
|
+
next_piece = @board.at(next_coords)
|
79
|
+
if next_piece.class == Pawn && next_piece == @last_piece &&
|
80
|
+
next_piece.moves_count == 1 && from[1].between?(3, 4)
|
81
|
+
return [from[0] + x, from[1] + pawn.direction]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chess_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anikeev Gennadiy
|
@@ -20,6 +20,13 @@ extra_rdoc_files: []
|
|
20
20
|
files:
|
21
21
|
- bin/chess_engine
|
22
22
|
- lib/chess_engine.rb
|
23
|
+
- lib/chess_engine/board.rb
|
24
|
+
- lib/chess_engine/cli.rb
|
25
|
+
- lib/chess_engine/game.rb
|
26
|
+
- lib/chess_engine/input.rb
|
27
|
+
- lib/chess_engine/move.rb
|
28
|
+
- lib/chess_engine/piece.rb
|
29
|
+
- lib/chess_engine/validator.rb
|
23
30
|
- spec/board_spec.rb
|
24
31
|
- spec/game_helper.rb
|
25
32
|
- spec/game_spec.rb
|