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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96485fa28f530a602da170422a2b08b757a674bef18cc89d995f620b91c9d4f0
4
- data.tar.gz: b9d5e4ba5d7e632bc9db2906667deee9c4b807e76371eff33022c3a9c88f5fc4
3
+ metadata.gz: ed27d3f266a1511e2db0726d00a983c73576324865e7c5b4aa6d936aa0934210
4
+ data.tar.gz: ced0a13a3ef74199cc0f74b1673ddf34260d3f53cba30af6321c24bc814c0f65
5
5
  SHA512:
6
- metadata.gz: 2eca066e4f4ad8c4afd56f82a580a40e7568f187ca4e68a1c914fab1ae3d16c48bf75e28fe8a9b8992bcb44db51618aa016440112be590b12b81f7e7cbe75b7d
7
- data.tar.gz: 8f20a87cd9d933ce5cd5b945937d7d3d208edf476633d4e0236f63ed25c262630161f01f5581d227ea8c893eb50bd8f308debe78021213fca11a8d0e0082d912
6
+ metadata.gz: dbea2e6b3f031712db02cb4a596bd8649b504b6127efb74ea139366e369d93d654847743167c995dc9fbfe26b00c12a8b564876ca011a0006a2274f7f8062f45
7
+ data.tar.gz: 42af13dabbc3f2d2204b0bcbd508122fec2096d80cb9ff2871aff82e4563e9c9f54a4000ebf634b230b477ad0d50a28423228aad8efb1889d357142e43b24d1b
@@ -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
- def [](column, row)
23
- @board[column][row]
24
- end
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
- 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
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
@@ -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]$/)
@@ -1,10 +1,15 @@
1
1
  require_relative "board"
2
- require_relative "validator"
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 = valid_move?(coords) ? @board.at(coords) : nil
177
+ piece = possible_move?(coords) ? @board.at(coords) : nil
127
178
  return true if !piece.nil? && piece.knight?
128
179
  end
129
180
  false
@@ -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 valid_move?(to)
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
- 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
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 valid_move?(coordinates)
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
- def valid_move?(coordinates)
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
@@ -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.8
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-14 00:00:00.000000000 Z
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
- rubyforge_project:
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