chess_engine 0.0.8 → 0.0.9
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 +56 -17
- data/lib/chess_engine/cli.rb +6 -0
- data/lib/chess_engine/game.rb +54 -3
- data/lib/chess_engine/move.rb +9 -0
- data/lib/chess_engine/{validator.rb → move_validator.rb} +41 -9
- data/spec/board_spec.rb +2 -2
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed27d3f266a1511e2db0726d00a983c73576324865e7c5b4aa6d936aa0934210
|
4
|
+
data.tar.gz: ced0a13a3ef74199cc0f74b1673ddf34260d3f53cba30af6321c24bc814c0f65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbea2e6b3f031712db02cb4a596bd8649b504b6127efb74ea139366e369d93d654847743167c995dc9fbfe26b00c12a8b564876ca011a0006a2274f7f8062f45
|
7
|
+
data.tar.gz: 42af13dabbc3f2d2204b0bcbd508122fec2096d80cb9ff2871aff82e4563e9c9f54a4000ebf634b230b477ad0d50a28423228aad8efb1889d357142e43b24d1b
|
data/lib/chess_engine/board.rb
CHANGED
@@ -2,11 +2,23 @@ require_relative "piece"
|
|
2
2
|
require "colorize"
|
3
3
|
|
4
4
|
module ChessEngine
|
5
|
+
##
|
6
|
+
# This class provides a data structure for the chess board.
|
7
|
+
# It is responsible for storing information about pieces positions and moving
|
8
|
+
# them. It doesn't implement move validations and chess rules,
|
9
|
+
# so it is possible to make any moves at this level of abstraction.
|
10
|
+
|
5
11
|
class Board
|
12
|
+
##
|
13
|
+
# Creates an empty 8x8 board as a 2-dimensional array
|
14
|
+
|
6
15
|
def initialize
|
7
16
|
@board = Array.new(8) { Array.new(8) { nil } }
|
8
17
|
end
|
9
18
|
|
19
|
+
##
|
20
|
+
# Sets the initial board position according to the classic chess rules
|
21
|
+
|
10
22
|
def set_default
|
11
23
|
[[:white, 0, 1], [:black, 7, 6]].each do |color, row1, row2|
|
12
24
|
["Rook", "Knight", "Elephant", "Queen", "King", "Elephant", "Knight", "Rook"].each.with_index do |class_name, column|
|
@@ -19,27 +31,39 @@ module ChessEngine
|
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
22
|
-
|
23
|
-
|
24
|
-
|
34
|
+
##
|
35
|
+
# Returns the piece string on the given position
|
36
|
+
# === Example
|
37
|
+
# at([0, 0]) #=> Rook:white
|
38
|
+
# at([3, 3]) #=> nil
|
25
39
|
|
26
|
-
def []=(column, row, piece)
|
27
|
-
@board[column][row] = piece
|
28
|
-
end
|
29
40
|
|
30
41
|
def at(coordinates)
|
31
42
|
return nil unless self.exists_at?(coordinates)
|
32
43
|
@board[coordinates[0]][coordinates[1]]
|
33
44
|
end
|
34
45
|
|
46
|
+
##
|
47
|
+
# Sets the board on given coordinates to +piece+
|
48
|
+
|
35
49
|
def set_at(coordinates, piece)
|
36
50
|
@board[coordinates[0]][coordinates[1]] = piece
|
37
51
|
end
|
38
52
|
|
53
|
+
##
|
54
|
+
# Checks if the values of +coordinates+ are between 0 and 7
|
55
|
+
# === Example
|
56
|
+
# exists_at?([0, 0]) #=> true
|
57
|
+
# exists_at?([8, -1]) #=> false
|
58
|
+
|
39
59
|
def exists_at?(coordinates)
|
40
60
|
coordinates.all? { |c| c.between?(0, 7) }
|
41
61
|
end
|
42
62
|
|
63
|
+
##
|
64
|
+
# Returns a string containing the board in printable format
|
65
|
+
# (uses colorize gem to paint the squares)
|
66
|
+
|
43
67
|
def to_s
|
44
68
|
string = ""
|
45
69
|
colors = [[:default, :light_white].cycle, [:light_white, :default].cycle].cycle
|
@@ -61,23 +85,17 @@ module ChessEngine
|
|
61
85
|
string
|
62
86
|
end
|
63
87
|
|
88
|
+
##
|
89
|
+
# Moves the value of +from+ coords to +to+ coords. Sets the value of +to+ to nil
|
90
|
+
|
64
91
|
def move_piece(from, to)
|
65
92
|
piece = self.at(from)
|
66
93
|
self.set_at(from, nil)
|
67
94
|
self.set_at(to, piece)
|
68
95
|
end
|
69
96
|
|
70
|
-
|
71
|
-
|
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
|
97
|
+
##
|
98
|
+
# Returns the coordinates of the king of given +color+
|
81
99
|
|
82
100
|
def king_coords(color)
|
83
101
|
Board.coordinates_list.find do |coord|
|
@@ -85,11 +103,32 @@ module ChessEngine
|
|
85
103
|
end
|
86
104
|
end
|
87
105
|
|
106
|
+
##
|
107
|
+
# Returns the array of coordinates where pieces of given +color+ a located
|
108
|
+
|
88
109
|
def piece_coordinates(color)
|
89
110
|
Board.coordinates_list.select do |coord|
|
90
111
|
piece = at(coord)
|
91
112
|
!piece.nil? && piece.color == color
|
92
113
|
end
|
93
114
|
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def [](column, row)
|
119
|
+
@board[column][row]
|
120
|
+
end
|
121
|
+
|
122
|
+
def []=(column, row, piece)
|
123
|
+
@board[column][row] = piece
|
124
|
+
end
|
125
|
+
|
126
|
+
def Board.coordinates_list
|
127
|
+
list = []
|
128
|
+
(0..7).each do |x|
|
129
|
+
(0..7).each { |y| list << [x, y]}
|
130
|
+
end
|
131
|
+
list
|
132
|
+
end
|
94
133
|
end
|
95
134
|
end
|
data/lib/chess_engine/cli.rb
CHANGED
@@ -6,9 +6,15 @@ require "yaml"
|
|
6
6
|
module ChessEngine
|
7
7
|
class NoGamesError < StandardError; end
|
8
8
|
|
9
|
+
##
|
10
|
+
# A Simple command line interface for the Chess Game
|
11
|
+
|
9
12
|
class CLI
|
10
13
|
include Input
|
11
14
|
|
15
|
+
##
|
16
|
+
# Starts a new game session
|
17
|
+
|
12
18
|
def initialize
|
13
19
|
begin
|
14
20
|
mode = get_input("\nChoose the game mode:\n1. New game\n2. Continue\nEnter your choice (1 or 2): ", /^[12]$/)
|
data/lib/chess_engine/game.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require_relative "board"
|
2
|
-
require_relative "
|
2
|
+
require_relative "move_validator"
|
3
3
|
require_relative "move"
|
4
4
|
|
5
5
|
module ChessEngine
|
6
6
|
class InvalidMove < StandardError; end
|
7
7
|
|
8
|
+
##
|
9
|
+
# This class provides all the rules for the chess game. It recognizes check,
|
10
|
+
# checkmate and stalemate. Move validations logic can be found in the
|
11
|
+
# MoveValidator module, which is included in this class
|
12
|
+
|
8
13
|
class Game
|
9
14
|
attr_accessor :name
|
10
15
|
attr_reader :current_color
|
@@ -19,6 +24,21 @@ module ChessEngine
|
|
19
24
|
@promotion_coord = false
|
20
25
|
end
|
21
26
|
|
27
|
+
##
|
28
|
+
# Accepts the move string in algebraic notation, e.g. "e2e4",
|
29
|
+
# and applies it to the board.
|
30
|
+
# Raises InvalidMove if:
|
31
|
+
# * Game is already over
|
32
|
+
# * Pawn promotion should be executed first
|
33
|
+
# * Empty square is chosen
|
34
|
+
# * Player tries to move piece of the opponent
|
35
|
+
# * Move is invalid (checks via the MoveValidator module)
|
36
|
+
# * Move is fatal (king is attacked after the move)
|
37
|
+
#
|
38
|
+
# After successfull move, the method changes the current player or
|
39
|
+
# goes into "A pawn needs promotion" state, which can be checked by
|
40
|
+
# #needs_promotion? method
|
41
|
+
|
22
42
|
def move(string)
|
23
43
|
from, to = Game.string_to_move(string)
|
24
44
|
piece = @board.at(from)
|
@@ -40,18 +60,36 @@ module ChessEngine
|
|
40
60
|
next_player
|
41
61
|
end
|
42
62
|
|
63
|
+
##
|
64
|
+
# Returns a piece (or nil if the square is empty) at given coordinates
|
65
|
+
# === Example
|
66
|
+
# g = Game.new
|
67
|
+
# g["e2"] #=> <Pawn ...>
|
68
|
+
# g["e4"] #=> nil
|
69
|
+
|
43
70
|
def [](str)
|
44
71
|
letters = ("a".."h").to_a
|
45
72
|
return nil unless /[a-h][1-8]/.match?(str)
|
46
73
|
@board.at([letters.find_index(str[0]), str[1].to_i - 1])
|
47
74
|
end
|
48
75
|
|
76
|
+
##
|
77
|
+
# Returns the board in the nice-looking string
|
78
|
+
|
49
79
|
def draw
|
50
80
|
@board.to_s
|
51
81
|
end
|
52
82
|
|
83
|
+
##
|
84
|
+
# Accepts a string with name of the piece.
|
85
|
+
# Promotes a pawn and changes the current player.
|
86
|
+
# Raises InvalidMove if promotion is not needed or invalid +class_name+
|
87
|
+
# has been passed
|
88
|
+
# === Example
|
89
|
+
# game.promotion("queen")
|
90
|
+
|
53
91
|
def promotion(class_name)
|
54
|
-
unless ["rook", "knight", "elephant", "queen"].include?(class_name.downcase)
|
92
|
+
unless needs_promotion? && ["rook", "knight", "elephant", "queen"].include?(class_name.downcase)
|
55
93
|
raise InvalidMove, "Invalid promotion"
|
56
94
|
end
|
57
95
|
@board.set_at(@promotion_coord, Module.const_get("ChessEngine::#{class_name.capitalize}").new(@current_color))
|
@@ -59,6 +97,10 @@ module ChessEngine
|
|
59
97
|
next_player
|
60
98
|
end
|
61
99
|
|
100
|
+
##
|
101
|
+
# Accepts a +length+ sybmol :short or :long. Ensures that castling is
|
102
|
+
# possible and commits appropriate moves. Otherwise, raises InvalidMove
|
103
|
+
|
62
104
|
def castling(length)
|
63
105
|
row = @current_color == :white ? 0 : 7
|
64
106
|
king = @board.at([4, row])
|
@@ -86,16 +128,25 @@ module ChessEngine
|
|
86
128
|
next_player
|
87
129
|
end
|
88
130
|
|
131
|
+
##
|
132
|
+
# Returns true if game is over
|
133
|
+
|
89
134
|
def over?
|
90
135
|
@board.piece_coordinates(@current_color).all? do |coord|
|
91
136
|
safe_moves(coord).empty?
|
92
137
|
end
|
93
138
|
end
|
94
139
|
|
140
|
+
##
|
141
|
+
# Checks if pawn promotion is needed
|
142
|
+
|
95
143
|
def needs_promotion?
|
96
144
|
!!@promotion_coord
|
97
145
|
end
|
98
146
|
|
147
|
+
##
|
148
|
+
# Returns true if current king is attacked
|
149
|
+
|
99
150
|
def check?
|
100
151
|
king_attacked?
|
101
152
|
end
|
@@ -123,7 +174,7 @@ module ChessEngine
|
|
123
174
|
[[1, 2], [2, 1], [1, -2], [-2, 1],
|
124
175
|
[-1, 2], [2, -1], [-1, -2], [-2, -1]].each do |move|
|
125
176
|
coords = relative_coords(king_coords, move)
|
126
|
-
piece =
|
177
|
+
piece = possible_move?(coords) ? @board.at(coords) : nil
|
127
178
|
return true if !piece.nil? && piece.knight?
|
128
179
|
end
|
129
180
|
false
|
data/lib/chess_engine/move.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module ChessEngine
|
2
|
+
##
|
3
|
+
# This class is made to make move canceling easier if something goes wrong.
|
4
|
+
|
2
5
|
class Move
|
3
6
|
def initialize(board, from, to)
|
4
7
|
@board = board
|
@@ -13,6 +16,9 @@ module ChessEngine
|
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
19
|
+
##
|
20
|
+
# Applies the move to the board
|
21
|
+
|
16
22
|
def commit
|
17
23
|
if en_passant?
|
18
24
|
@board.set_at(@en_passant_coord, nil)
|
@@ -20,6 +26,9 @@ module ChessEngine
|
|
20
26
|
@board.move_piece(@from, @to)
|
21
27
|
end
|
22
28
|
|
29
|
+
##
|
30
|
+
# Moves pieces back and returns the board to the previous state
|
31
|
+
|
23
32
|
def rollback
|
24
33
|
@original_squares.each do |square|
|
25
34
|
@board.set_at(square[:coord], square[:piece])
|
@@ -1,15 +1,28 @@
|
|
1
1
|
module ChessEngine
|
2
|
+
##
|
3
|
+
# This module contains all the methods needed to check if
|
4
|
+
# some move is valid or not. It is included in the Game class and so uses
|
5
|
+
# some of its attributes: board, current_color and last_piece (for en passant only)
|
6
|
+
|
2
7
|
module MoveValidator
|
8
|
+
|
9
|
+
## Excludes from valid_moves all fatal moves
|
10
|
+
|
3
11
|
def safe_moves(from)
|
4
12
|
valid_moves(from).reject { |move| fatal_move?(from, move) }
|
5
13
|
end
|
6
14
|
|
15
|
+
##
|
16
|
+
# Returns an array of valid moves for a piece at the given position.
|
17
|
+
# Note: this method doesn't exclude moves that lead current king to be attacked
|
18
|
+
# (See +#safe_moves+ method)
|
19
|
+
|
7
20
|
def valid_moves(from)
|
8
21
|
piece = @board.at(from)
|
9
22
|
if piece.king? || piece.knight?
|
10
23
|
piece.moves.map do |move|
|
11
24
|
to = relative_coords(from, move)
|
12
|
-
to if
|
25
|
+
to if possible_move?(to)
|
13
26
|
end.compact
|
14
27
|
elsif piece.pawn?
|
15
28
|
pawn_valid_moves(from)
|
@@ -18,25 +31,34 @@ module ChessEngine
|
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
34
|
+
##
|
35
|
+
# Returns an array of coordinates that can be reached by recursively
|
36
|
+
# applying the given +move+, starting from the +from+ coordinates
|
37
|
+
|
38
|
+
private
|
27
39
|
|
28
40
|
def repeated_move(from, move, valid_moves = [])
|
29
41
|
coordinates = relative_coords(from, move)
|
30
|
-
return valid_moves unless
|
42
|
+
return valid_moves unless possible_move?(coordinates)
|
31
43
|
return valid_moves << coordinates unless @board.at(coordinates).nil?
|
32
44
|
repeated_move(coordinates, move, valid_moves << coordinates)
|
33
45
|
end
|
34
46
|
|
47
|
+
##
|
48
|
+
# Returns coordinates that will be reached after applying the +move+,
|
49
|
+
# starting from the +from+ coordinates
|
50
|
+
|
35
51
|
def relative_coords(from, move)
|
36
52
|
[from[0] + move[0], from[1] + move[1]]
|
37
53
|
end
|
38
54
|
|
39
|
-
|
55
|
+
##
|
56
|
+
# Returns true if:
|
57
|
+
# * The 8x8 board exists at given coordinates
|
58
|
+
# * Board at given coordinates is empty or it contains a piece with the same
|
59
|
+
# color as the current_color
|
60
|
+
|
61
|
+
def possible_move?(coordinates)
|
40
62
|
if @board.exists_at?(coordinates)
|
41
63
|
piece = @board.at(coordinates)
|
42
64
|
return (piece.nil? || piece.color != @current_color)
|
@@ -44,6 +66,9 @@ module ChessEngine
|
|
44
66
|
return false
|
45
67
|
end
|
46
68
|
|
69
|
+
##
|
70
|
+
# Returns true if the current king is attacked after the given move
|
71
|
+
|
47
72
|
def fatal_move?(from, to)
|
48
73
|
is_fatal = false
|
49
74
|
move = Move.new(@board, from, to)
|
@@ -83,5 +108,12 @@ module ChessEngine
|
|
83
108
|
end
|
84
109
|
nil
|
85
110
|
end
|
111
|
+
|
112
|
+
def valid_moves_recursive(from)
|
113
|
+
piece = @board.at(from)
|
114
|
+
piece.moves.inject([]) do |valid_moves, move|
|
115
|
+
valid_moves.push(*repeated_move(from, move))
|
116
|
+
end
|
117
|
+
end
|
86
118
|
end
|
87
119
|
end
|
data/spec/board_spec.rb
CHANGED
@@ -41,8 +41,8 @@ describe ChessEngine::Board do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "moves piece between two points" do
|
44
|
-
expect(@board[4, 1]).to be_nil
|
45
|
-
expect(@board[4, 3].pawn?).to eq(true)
|
44
|
+
expect(@board.at([4, 1])).to be_nil
|
45
|
+
expect(@board.at([4, 3]).pawn?).to eq(true)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anikeev Gennadiy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: This library provides all the rules of the chess game. Also it provides
|
14
14
|
a command line interface with serialization features
|
@@ -25,8 +25,8 @@ files:
|
|
25
25
|
- lib/chess_engine/game.rb
|
26
26
|
- lib/chess_engine/input.rb
|
27
27
|
- lib/chess_engine/move.rb
|
28
|
+
- lib/chess_engine/move_validator.rb
|
28
29
|
- lib/chess_engine/piece.rb
|
29
|
-
- lib/chess_engine/validator.rb
|
30
30
|
- spec/board_spec.rb
|
31
31
|
- spec/game_helper.rb
|
32
32
|
- spec/game_spec.rb
|
@@ -50,8 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
50
|
- !ruby/object:Gem::Version
|
51
51
|
version: '0'
|
52
52
|
requirements: []
|
53
|
-
|
54
|
-
rubygems_version: 2.7.6
|
53
|
+
rubygems_version: 3.0.3
|
55
54
|
signing_key:
|
56
55
|
specification_version: 4
|
57
56
|
summary: Simple chess library that uses algebraic notation
|