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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +45 -0
  4. data/LICENSE +21 -0
  5. data/README.md +24 -0
  6. data/Rakefile +1 -0
  7. data/bin/sapphire-chess +5 -0
  8. data/lib/sapphire-chess/ai.rb +101 -0
  9. data/lib/sapphire-chess/algebraic_conversion.rb +89 -0
  10. data/lib/sapphire-chess/board/board_analysis.rb +66 -0
  11. data/lib/sapphire-chess/board/board_evaluation.rb +25 -0
  12. data/lib/sapphire-chess/board/board_general.rb +155 -0
  13. data/lib/sapphire-chess/board/board_provisional_moves.rb +32 -0
  14. data/lib/sapphire-chess/board/board_renderer.rb +122 -0
  15. data/lib/sapphire-chess/board.rb +5 -0
  16. data/lib/sapphire-chess/display.rb +140 -0
  17. data/lib/sapphire-chess/engine.rb +153 -0
  18. data/lib/sapphire-chess/human_input_validation.rb +123 -0
  19. data/lib/sapphire-chess/movement_rules/castling_board_control.rb +37 -0
  20. data/lib/sapphire-chess/movement_rules/castling_piece_control.rb +9 -0
  21. data/lib/sapphire-chess/movement_rules/castling_rights.rb +79 -0
  22. data/lib/sapphire-chess/movement_rules/en_passant_board_control.rb +20 -0
  23. data/lib/sapphire-chess/movement_rules/en_passant_piece_control.rb +36 -0
  24. data/lib/sapphire-chess/movement_rules/move_slide_pattern.rb +23 -0
  25. data/lib/sapphire-chess/movement_rules/move_step_pattern.rb +17 -0
  26. data/lib/sapphire-chess/movement_rules/pawn_movement_and_promotion.rb +71 -0
  27. data/lib/sapphire-chess/movement_rules.rb +7 -0
  28. data/lib/sapphire-chess/pieces/bishop.rb +56 -0
  29. data/lib/sapphire-chess/pieces/empty_square.rb +13 -0
  30. data/lib/sapphire-chess/pieces/king.rb +77 -0
  31. data/lib/sapphire-chess/pieces/knight.rb +57 -0
  32. data/lib/sapphire-chess/pieces/pawn.rb +82 -0
  33. data/lib/sapphire-chess/pieces/piece.rb +77 -0
  34. data/lib/sapphire-chess/pieces/queen.rb +44 -0
  35. data/lib/sapphire-chess/pieces/rook.rb +62 -0
  36. data/lib/sapphire-chess/pieces.rb +8 -0
  37. data/lib/sapphire-chess/player.rb +43 -0
  38. data/lib/sapphire-chess/version.rb +3 -0
  39. data/lib/sapphire-chess.rb +9 -0
  40. data/sapphire-chess.gemspec +29 -0
  41. metadata +142 -0
@@ -0,0 +1,36 @@
1
+ module EnPassantPieceControl
2
+ def add_en_passant_movement!(moves)
3
+ adyacent_enemy_pawn = pawn_to_pass.first
4
+ return if adyacent_enemy_pawn.nil?
5
+
6
+ moves << en_passant_target_square(adyacent_enemy_pawn)
7
+ end
8
+
9
+ def pawn_to_pass(current_square = location)
10
+ # See Piece#safe_moves, Board#is_a_duplicate?
11
+ return [] if board.a_duplicate?
12
+
13
+ left_square = [current_square.first, current_square.last - 1]
14
+ right_square = [current_square.first, current_square.last + 1]
15
+
16
+ [left_square, right_square].select do |square|
17
+ board[square].is_a?(Pawn) && pawn_just_moved_two?(square)
18
+ end
19
+ end
20
+
21
+ def pawn_just_moved_two?(square)
22
+ if color == :white
23
+ board.black_player.history.last ==
24
+ [[square.first - 2, square.last], square]
25
+ else
26
+ board.white_player.history.last ==
27
+ [[square.first + 2, square.last], square]
28
+ end
29
+ end
30
+
31
+ def en_passant_target_square(adyacent_enemy_pawn)
32
+ direction = color == :white ? -1 : 1
33
+
34
+ [adyacent_enemy_pawn.first + direction, adyacent_enemy_pawn.last]
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ module SlidePattern
2
+ def available_moves
3
+ move_directions.each_with_object([]) do |(row_direction, column_direction), moves|
4
+ current_row, current_column = location
5
+
6
+ loop do
7
+ current_row += row_direction
8
+ current_column += column_direction
9
+ possible_location = [current_row, current_column]
10
+
11
+ break unless board.within_limits?(possible_location)
12
+ break if friend_in?(possible_location)
13
+
14
+ moves << possible_location if board.empty_square?(possible_location)
15
+
16
+ if enemy_in?(possible_location)
17
+ moves << possible_location
18
+ break
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module StepPattern
2
+ def available_moves
3
+ move_directions.each_with_object([]) do |(row_direction, column_direction), moves|
4
+ current_row, current_column = location
5
+
6
+ current_row += row_direction
7
+ current_column += column_direction
8
+ possible_location = [current_row, current_column]
9
+
10
+ next unless board.within_limits?(possible_location)
11
+
12
+ if board.empty_square?(possible_location) || enemy_in?(possible_location)
13
+ moves << possible_location
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ module PawnMovementAndPromotion
2
+ def available_moves
3
+ if promoted? then SlidePattern::available_moves
4
+ else
5
+ moves = []
6
+ add_one_square_movement!(moves)
7
+ add_two_square_movement!(moves)
8
+ add_diagonal_movement!(moves)
9
+ add_en_passant_movement!(moves)
10
+
11
+ moves.select { |move| board.within_limits?(move) }
12
+ end
13
+ end
14
+
15
+ def promoted?
16
+ @promoted
17
+ end
18
+
19
+ private
20
+
21
+ attr_writer :promoted
22
+
23
+ def promote
24
+ self.promoted = true if location.first == opposite_row
25
+ end
26
+
27
+ def opposite_row
28
+ case color
29
+ when :white then self.class::W_OPPOSITE_ROW
30
+ else self.class::B_OPPOSITE_ROW
31
+ end
32
+ end
33
+
34
+ def at_start?
35
+ start_row = (color == :white ? Board::W_PAWN_ROW : Board::B_PAWN_ROW)
36
+
37
+ current_row == start_row
38
+ end
39
+
40
+ def forward_direction
41
+ color == :white ? -1 : 1
42
+ end
43
+
44
+ def add_one_square_movement!(moves)
45
+ current_row, current_column = location
46
+
47
+ one_forward = [current_row + forward_direction, current_column]
48
+ moves << one_forward if board.empty_square?(one_forward)
49
+ end
50
+
51
+ def add_two_square_movement!(moves)
52
+ current_row, current_column = location
53
+
54
+ one_forward = [current_row + forward_direction, current_column]
55
+ two_forward = [current_row + (forward_direction * 2), current_column]
56
+ if board.empty_square?(two_forward) &&
57
+ board.empty_square?(one_forward) &&
58
+ at_start?
59
+ moves << two_forward
60
+ end
61
+ end
62
+
63
+ def add_diagonal_movement!(moves)
64
+ current_row, current_column = location
65
+
66
+ diagonal_left = [current_row + forward_direction, current_column + 1]
67
+ diagonal_right = [current_row + forward_direction, current_column - 1]
68
+ moves << diagonal_left if enemy_in?(diagonal_left)
69
+ moves << diagonal_right if enemy_in?(diagonal_right)
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'movement_rules/castling_board_control'
2
+ require_relative 'movement_rules/castling_piece_control'
3
+ require_relative 'movement_rules/castling_rights'
4
+ require_relative 'movement_rules/en_passant_board_control'
5
+ require_relative 'movement_rules/en_passant_piece_control'
6
+ require_relative 'movement_rules/move_slide_pattern'
7
+ require_relative 'movement_rules/move_step_pattern'
@@ -0,0 +1,56 @@
1
+ class Bishop < Piece
2
+ MOVE_DIRECTIONS = [
3
+ [1, 1], [1, -1], [-1, 1], [-1, -1]
4
+ ].freeze
5
+
6
+ BLACK = '♝'
7
+ WHITE = '♗'
8
+
9
+ VALUE = 330
10
+
11
+ WHITE_LOCATION_VALUE = [
12
+ [-20, -10, -10, -10, -10, -10, -10, -20],
13
+ [-10, 0, 0, 0, 0, 0, 0, -10],
14
+ [-10, 0, 5, 10, 10, 5, 0, -10],
15
+ [-10, 5, 5, 10, 10, 5, 5, -10],
16
+ [-10, 0, 10, 10, 10, 10, 0, -10],
17
+ [-10, 10, 10, 10, 10, 10, 10, -10],
18
+ [-10, 5, 0, 0, 0, 0, 5, -10],
19
+ [-20, -10, -10, -10, -10, -10, -10, -20]
20
+ ].freeze
21
+
22
+ BLACK_LOCATION_VALUE = [
23
+ [-20, -10, -10, -10, -10, -10, -10, -20],
24
+ [-10, 5, 0, 0, 0, 0, 5, -10],
25
+ [-10, 10, 10, 10, 10, 10, 10, -10],
26
+ [-10, 0, 10, 10, 10, 10, 0, -10],
27
+ [-10, 5, 5, 10, 10, 5, 5, -10],
28
+ [-10, 0, 5, 10, 10, 5, 0, -10],
29
+ [-10, 0, 0, 0, 0, 0, 0, -10],
30
+ [-20, -10, -10, -10, -10, -10, -10, -20]
31
+ ].freeze
32
+
33
+ WHITE_LOCATION_VALUE_EASY = [
34
+ [-20, -10, -10, -10, -10, -10, -10, -20],
35
+ [-10, 0, 0, 0, 0, 0, 0, -10],
36
+ [-10, 0, 5, 15, 15, 5, 0, -10],
37
+ [-10, 5, 5, 20, 20, 5, 5, -10],
38
+ [-10, 0, 15, 20, 20, 15, 0, -10],
39
+ [-10, 10, 15, 20, 20, 15, 10, -10],
40
+ [-10, 5, 0, 0, 0, 0, 5, -10],
41
+ [-20, -10, -10, -10, -10, -10, -10, -20]
42
+ ].freeze
43
+
44
+ BLACK_LOCATION_VALUE_EASY = [
45
+ [-20, -10, -10, -10, -10, -10, -10, -20],
46
+ [-10, 5, 0, 0, 0, 0, 5, -10],
47
+ [-10, 10, 15, 20, 20, 15, 10, -10],
48
+ [-10, 0, 15, 20, 20, 15, 0, -10],
49
+ [-10, 5, 5, 20, 20, 5, 5, -10],
50
+ [-10, 0, 5, 15, 15, 5, 0, -10],
51
+ [-10, 0, 0, 0, 0, 0, 0, -10],
52
+ [-20, -10, -10, -10, -10, -10, -10, -20]
53
+ ].freeze
54
+
55
+ include SlidePattern
56
+ end
@@ -0,0 +1,13 @@
1
+ require 'singleton'
2
+
3
+ class EmptySquare
4
+ include Singleton
5
+
6
+ def to_s
7
+ ' '
8
+ end
9
+
10
+ def white
11
+ '██'
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ class King < Piece
2
+ MOVE_DIRECTIONS = [
3
+ [0, 1], [1, 1], [1, 0], [0, -1],
4
+ [1, -1], [-1, 1], [-1, -1], [-1, 0]
5
+ ].freeze
6
+
7
+ BLACK = '♚'
8
+ WHITE = '♔'
9
+
10
+ VALUE = 20_000
11
+
12
+ WHITE_LOCATION_VALUE = [
13
+ [-30, -40, -40, -50, -50, -40, -40, -30],
14
+ [-30, -40, -40, -50, -50, -40, -40, -30],
15
+ [-30, -40, -40, -50, -50, -40, -40, -30],
16
+ [-30, -40, -40, -50, -50, -40, -40, -30],
17
+ [-20, -30, -30, -40, -40, -30, -30, -20],
18
+ [-10, -20, -20, -20, -20, -20, -20, -10],
19
+ [20, 20, 0, 0, 0, 0, 20, 20],
20
+ [20, 30, 10, 0, 0, 10, 30, 20]
21
+ ].freeze
22
+
23
+ BLACK_LOCATION_VALUE = [
24
+ [20, 30, 10, 0, 0, 10, 30, 20],
25
+ [20, 20, 0, 0, 0, 0, 20, 20],
26
+ [-10, -20, -20, -20, -20, -20, -20, -10],
27
+ [-20, -30, -30, -40, -40, -30, -30, -20],
28
+ [-30, -40, -40, -50, -50, -40, -40, -30],
29
+ [-30, -40, -40, -50, -50, -40, -40, -30],
30
+ [-30, -40, -40, -50, -50, -40, -40, -30],
31
+ [-30, -40, -40, -50, -50, -40, -40, -30]
32
+ ].freeze
33
+
34
+ WHITE_LOCATION_VALUE_END = [
35
+ [-50, -40, -30, -20, -20, -30, -40, -50],
36
+ [-30, -20, -10, 0, 0, -10, -20, -30],
37
+ [-30, -10, 20, 30, 30, 20, -10, -30],
38
+ [-30, -10, 30, 40, 40, 30, -10, -30],
39
+ [-30, -10, 30, 40, 40, 30, -10, -30],
40
+ [-30, -10, 20, 30, 30, 20, -10, -30],
41
+ [-30, -30, 0, 0, 0, 0, -30, -30],
42
+ [-50, -30, -30, -30, -30, -30, -30, -50]
43
+ ].freeze
44
+
45
+ BLACK_LOCATION_VALUE_END = [
46
+ [-50, -30, -30, -30, -30, -30, -30, -50],
47
+ [-30, -30, 0, 0, 0, 0, -30, -30],
48
+ [-30, -10, 20, 30, 30, 20, -10, -30],
49
+ [-30, -10, 30, 40, 40, 30, -10, -30],
50
+ [-30, -10, 30, 40, 40, 30, -10, -30],
51
+ [-30, -10, 20, 30, 30, 20, -10, -30],
52
+ [-30, -20, -10, 0, 0, -10, -20, -30],
53
+ [-50, -40, -30, -20, -20, -30, -40, -50]
54
+ ].freeze
55
+
56
+ include StepPattern
57
+ include CastlingPieceControl
58
+
59
+ def initialize(board, location, color)
60
+ super
61
+ @moved = false
62
+ end
63
+
64
+ def location_value
65
+ row, column = location
66
+
67
+ location_value_table =
68
+ if board.end_game? && color == :white then self.class::WHITE_LOCATION_VALUE_END
69
+ elsif board.end_game? && color == :black then self.class::BLACK_LOCATION_VALUE_END
70
+ elsif !board.end_game? && color == :white then self.class::WHITE_LOCATION_VALUE
71
+ else
72
+ self.class::BLACK_LOCATION_VALUE
73
+ end
74
+
75
+ location_value_table[row][column]
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ class Knight < Piece
2
+ MOVE_DIRECTIONS = [
3
+ [1, 2], [2, 1], [-1, 2], [-2, 1],
4
+ [1, -2], [2, -1], [-1, -2], [-2, -1]
5
+ ].freeze
6
+
7
+ BLACK = '♞'
8
+ WHITE = '♘'
9
+
10
+ VALUE = 320
11
+
12
+ WHITE_LOCATION_VALUE = [
13
+ [-50, -40, -30, -30, -30, -30, -40, -50],
14
+ [-40, -20, 0, 0, 0, 0, -20, -40],
15
+ [-30, 0, 10, 15, 15, 10, 0, -30],
16
+ [-30, 5, 15, 20, 20, 15, 5, -30],
17
+ [-30, 0, 15, 20, 20, 15, 0, -30],
18
+ [-30, 5, 10, 15, 15, 10, 5, -30],
19
+ [-40, -20, 0, 5, 5, 0, -20, -40],
20
+ [-50, -40, -30, -30, -30, -30, -40, -50]
21
+ ].freeze
22
+
23
+ BLACK_LOCATION_VALUE = [
24
+ [-50, -40, -30, -30, -30, -30, -40, -50],
25
+ [-40, -20, 0, 5, 5, 0, -20, -40],
26
+ [-30, 5, 10, 15, 15, 10, 5, -30],
27
+ [-30, 0, 15, 20, 20, 15, 0, -30],
28
+ [-30, 5, 15, 20, 20, 15, 5, -30],
29
+ [-30, 0, 10, 15, 15, 10, 0, -30],
30
+ [-40, -20, 0, 0, 0, 0, -20, -40],
31
+ [-50, -40, -30, -30, -30, -30, -40, -50]
32
+ ].freeze
33
+
34
+ WHITE_LOCATION_VALUE_EASY = [
35
+ [-50, -40, -30, -30, -30, -30, -40, -50],
36
+ [-40, -20, 0, 0, 0, 0, -20, -40],
37
+ [-30, 0, 20, 25, 25, 20, 0, -30],
38
+ [-30, 5, 25, 40, 40, 25, 5, -30],
39
+ [-30, 0, 25, 40, 40, 25, 0, -30],
40
+ [-30, 5, 20, 25, 25, 20, 5, -30],
41
+ [-40, -20, 0, 5, 5, 0, -20, -40],
42
+ [-50, -40, -30, -30, -30, -30, -40, -50]
43
+ ].freeze
44
+
45
+ BLACK_LOCATION_VALUE_EASY = [
46
+ [-50, -40, -30, -30, -30, -30, -40, -50],
47
+ [-40, -20, 0, 5, 5, 0, -20, -40],
48
+ [-30, 0, 20, 25, 25, 20, 0, -30],
49
+ [-30, 5, 25, 40, 40, 25, 5, -30],
50
+ [-30, 0, 25, 40, 40, 25, 0, -30],
51
+ [-30, 5, 20, 25, 25, 20, 5, -30],
52
+ [-40, -20, 0, 0, 0, 0, -20, -40],
53
+ [-50, -40, -30, -30, -30, -30, -40, -50]
54
+ ].freeze
55
+
56
+ include StepPattern
57
+ end
@@ -0,0 +1,82 @@
1
+ require_relative '../movement_rules/en_passant_piece_control'
2
+ require_relative '../movement_rules/pawn_movement_and_promotion'
3
+
4
+ class Pawn < Piece
5
+ BLACK = ['♟', '♛'].freeze
6
+ WHITE = ['♙', '♕'].freeze
7
+
8
+ B_OPPOSITE_ROW = 7
9
+ W_OPPOSITE_ROW = 0
10
+
11
+ MOVE_DIRECTIONS = [
12
+ [0, 1], [0, -1], [1, 0], [-1, 0],
13
+ [1, 1], [1, -1], [-1, 1], [-1, -1]
14
+ ].freeze
15
+
16
+ VALUE = 100
17
+
18
+ WHITE_LOCATION_VALUE = [
19
+ [0, 0, 0, 0, 0, 0, 0, 0],
20
+ [50, 50, 50, 50, 50, 50, 50, 50],
21
+ [10, 10, 20, 30, 30, 20, 10, 10],
22
+ [5, 5, 10, 25, 25, 10, 5, 5],
23
+ [0, 0, 0, 20, 20, 0, 0, 0],
24
+ [5, -5, -10, 0, 0, -10, -5, 5],
25
+ [5, 10, 10, -20, -20, 10, 10, 5],
26
+ [0, 0, 0, 0, 0, 0, 0, 0]
27
+ ].freeze
28
+
29
+ BLACK_LOCATION_VALUE = [
30
+ [0, 0, 0, 0, 0, 0, 0, 0],
31
+ [5, 10, 10, -20, -20, 10, 10, 5],
32
+ [5, -5, -10, 0, 0, -10, -5, 5],
33
+ [0, 0, 0, 20, 20, 0, 0, 0],
34
+ [5, 5, 10, 25, 25, 10, 5, 5],
35
+ [10, 10, 20, 30, 30, 20, 10, 10],
36
+ [50, 50, 50, 50, 50, 50, 50, 50],
37
+ [0, 0, 0, 0, 0, 0, 0, 0]
38
+ ].freeze
39
+
40
+ WHITE_LOCATION_VALUE_EASY = [
41
+ [100, 100, 100, 100, 100, 100, 100, 100],
42
+ [60, 60, 60, 60, 60, 60, 60, 60],
43
+ [50, 50, 60, 60, 60, 60, 50, 50],
44
+ [50, 50, 60, 60, 60, 60, 50, 50],
45
+ [50, 50, 60, 0, 0, 60, 50, 50],
46
+ [50, 50, 10, 0, 0, 10, 50, 50],
47
+ [5, 10, 10, -20, -20, 10, 10, 5],
48
+ [0, 0, 0, 0, 0, 0, 0, 0]
49
+ ].freeze
50
+
51
+ BLACK_LOCATION_VALUE_EASY = [
52
+ [0, 0, 0, 0, 0, 0, 0, 0],
53
+ [5, 10, 10, -20, -20, 10, 10, 5],
54
+ [50, 50, 10, 0, 0, 10, 50, 50],
55
+ [50, 50, 60, 0, 0, 60, 50, 50],
56
+ [50, 50, 60, 60, 60, 60, 50, 50],
57
+ [50, 50, 60, 60, 60, 60, 50, 50],
58
+ [60, 60, 60, 60, 60, 60, 60, 60],
59
+ [100, 100, 100, 100, 100, 100, 100, 100]
60
+ ].freeze
61
+
62
+ include SlidePattern
63
+ include EnPassantPieceControl
64
+ include PawnMovementAndPromotion
65
+
66
+ def initialize(board, location, color)
67
+ super(board, location, color)
68
+ @promoted = false
69
+ end
70
+
71
+ def to_s
72
+ promote unless promoted?
73
+
74
+ white = color == :white
75
+ if promoted? && white then Paint[self.class::WHITE.last, :white, :bright]
76
+ elsif promoted? && !white then Paint[self.class::BLACK.last, :blue, :bright]
77
+ elsif !promoted? && white then Paint[self.class::WHITE.first, :white]
78
+ else
79
+ Paint[self.class::BLACK.first, :blue]
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,77 @@
1
+ require_relative '../board'
2
+ require_relative '../movement_rules/castling_piece_control'
3
+ require_relative '../movement_rules/move_slide_pattern'
4
+ require_relative '../movement_rules/move_step_pattern'
5
+
6
+ require 'bundler/setup'
7
+ require 'paint'
8
+
9
+ class Piece
10
+ attr_reader :color, :board
11
+ attr_accessor :location
12
+
13
+ def initialize(board, location, color)
14
+ @board = board
15
+ @color = color
16
+ @location = location
17
+ end
18
+
19
+ def to_s
20
+ case color
21
+ when :white then Paint[self.class::WHITE, :white]
22
+ else Paint[self.class::BLACK, :blue]
23
+ end
24
+ end
25
+
26
+ # Available moves that don't move us into check
27
+ def safe_moves
28
+ available_moves.each_with_object([]) do |move, moves|
29
+ new_board = board.duplicate
30
+
31
+ new_board.move_piece!(location, move)
32
+ moves << move unless new_board.in_check?(color)
33
+ end
34
+ end
35
+
36
+ def value
37
+ self.class::VALUE
38
+ end
39
+
40
+ def location_value
41
+ row, column = location
42
+ white = color == :white
43
+ if board.hard_difficulty? && white
44
+ self.class::WHITE_LOCATION_VALUE[row][column]
45
+ elsif board.hard_difficulty? && !white
46
+ self.class::BLACK_LOCATION_VALUE[row][column]
47
+ elsif !board.hard_difficulty? && white
48
+ self.class::WHITE_LOCATION_VALUE_EASY[row][column]
49
+ else
50
+ self.class::BLACK_LOCATION_VALUE_EASY[row][column]
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def current_row
57
+ location.first
58
+ end
59
+
60
+ def current_column
61
+ location.last
62
+ end
63
+
64
+ def move_directions
65
+ self.class::MOVE_DIRECTIONS
66
+ end
67
+
68
+ def enemy_in?(location)
69
+ board.within_limits?(location) &&
70
+ board[location].is_a?(Piece) &&
71
+ board[location].color != color
72
+ end
73
+
74
+ def friend_in?(location)
75
+ board[location].is_a?(Piece) && board[location].color == color
76
+ end
77
+ end
@@ -0,0 +1,44 @@
1
+ class Queen < Piece
2
+ MOVE_DIRECTIONS = [
3
+ [0, 1], [0, -1], [1, 0], [-1, 0],
4
+ [1, 1], [1, -1], [-1, 1], [-1, -1]
5
+ ].freeze
6
+
7
+ BLACK = '♛'
8
+ WHITE = '♕'
9
+
10
+ VALUE = 900
11
+
12
+ WHITE_LOCATION_VALUE = [
13
+ [-20, -10, -10, -5, -5, -10, -10, -20],
14
+ [-10, 0, 0, 0, 0, 0, 0, -10],
15
+ [-10, 0, 5, 5, 5, 5, 0, -10],
16
+ [-5, 0, 5, 5, 5, 5, 0, -5],
17
+ [0, 0, 5, 5, 5, 5, 0, -5],
18
+ [-10, 5, 5, 5, 5, 5, 0, -10],
19
+ [-10, 0, 5, 0, 0, 0, 0, -10],
20
+ [-20, -10, -10, -5, -5, -10, -10, -20]
21
+ ].freeze
22
+
23
+ BLACK_LOCATION_VALUE = [
24
+ [-20, -10, -10, -5, -5, -10, -10, -20],
25
+ [-10, 0, 0, 0, 0, 5, 0, -10],
26
+ [-10, 0, 5, 5, 5, 5, 5, -10],
27
+ [-5, 0, 5, 5, 5, 5, 0, 0],
28
+ [-5, 0, 5, 5, 5, 5, 0, -5],
29
+ [-10, 0, 5, 5, 5, 5, 0, -10],
30
+ [-10, 0, 0, 0, 0, 0, 0, -10],
31
+ [-20, -10, -10, -5, -5, -10, -10, -20]
32
+ ].freeze
33
+
34
+ include SlidePattern
35
+
36
+ def location_value
37
+ row, column = location
38
+
39
+ case color
40
+ when :white then self.class::WHITE_LOCATION_VALUE[row][column]
41
+ else self.class::BLACK_LOCATION_VALUE[row][column]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ class Rook < Piece
2
+ MOVE_DIRECTIONS = [
3
+ [0, 1], [0, -1], [1, 0], [-1, 0]
4
+ ].freeze
5
+
6
+ BLACK = '♜'
7
+ WHITE = '♖'
8
+
9
+ VALUE = 500
10
+
11
+ WHITE_LOCATION_VALUE = [
12
+ [0, 0, 0, 0, 0, 0, 0, 0],
13
+ [5, 10, 10, 10, 10, 10, 10, 5],
14
+ [-5, 0, 0, 0, 0, 0, 0, -5],
15
+ [-5, 0, 0, 0, 0, 0, 0, -5],
16
+ [-5, 0, 0, 0, 0, 0, 0, -5],
17
+ [-5, 0, 0, 0, 0, 0, 0, -5],
18
+ [-5, 0, 0, 0, 0, 0, 0, -5],
19
+ [0, 0, 0, 5, 5, 0, 0, 0]
20
+ ].freeze
21
+
22
+ BLACK_LOCATION_VALUE = [
23
+ [0, 0, 0, 5, 5, 0, 0, 0],
24
+ [-5, 0, 0, 0, 0, 0, 0, -5],
25
+ [-5, 0, 0, 0, 0, 0, 0, -5],
26
+ [-5, 0, 0, 0, 0, 0, 0, -5],
27
+ [-5, 0, 0, 0, 0, 0, 0, -5],
28
+ [-5, 0, 0, 0, 0, 0, 0, -5],
29
+ [5, 10, 10, 10, 10, 10, 10, 5],
30
+ [0, 0, 0, 0, 0, 0, 0, 0]
31
+ ].freeze
32
+
33
+ WHITE_LOCATION_VALUE_EASY = [
34
+ [0, 0, 0, 0, 0, 0, 0, 0],
35
+ [5, 10, 10, 10, 10, 10, 10, 5],
36
+ [-5, 0, 10, 10, 10, 10, 0, -5],
37
+ [-5, 0, 10, 10, 10, 10, 0, -5],
38
+ [-5, 0, 10, 10, 10, 10, 0, -5],
39
+ [-5, 0, 10, 10, 10, 10, 0, -5],
40
+ [-5, 0, 0, 0, 0, 0, 0, -5],
41
+ [0, 0, 0, 5, 5, 0, 0, 0]
42
+ ].freeze
43
+
44
+ BLACK_LOCATION_VALUE_EASY = [
45
+ [0, 0, 0, 5, 5, 0, 0, 0],
46
+ [-5, 0, 0, 0, 0, 0, 0, -5],
47
+ [-5, 0, 10, 10, 10, 10, 0, -5],
48
+ [-5, 0, 10, 10, 10, 10, 0, -5],
49
+ [-5, 0, 10, 10, 10, 10, 0, -5],
50
+ [-5, 0, 10, 10, 10, 10, 0, -5],
51
+ [5, 10, 10, 10, 10, 10, 10, 5],
52
+ [0, 0, 0, 0, 0, 0, 0, 0]
53
+ ].freeze
54
+
55
+ include SlidePattern
56
+ include CastlingPieceControl
57
+
58
+ def initialize(board, location, color)
59
+ super
60
+ @moved = false
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'pieces/empty_square.rb'
2
+ require_relative 'pieces/piece.rb'
3
+ require_relative 'pieces/pawn.rb'
4
+ require_relative 'pieces/knight.rb'
5
+ require_relative 'pieces/bishop.rb'
6
+ require_relative 'pieces/rook.rb'
7
+ require_relative 'pieces/queen.rb'
8
+ require_relative 'pieces/king.rb'
@@ -0,0 +1,43 @@
1
+ require_relative 'ai'
2
+ require_relative 'algebraic_conversion'
3
+ require_relative 'movement_rules/castling_rights'
4
+
5
+ class Player
6
+ include CastlingRights
7
+
8
+ attr_accessor :last_move
9
+ attr_reader :color, :board, :history
10
+
11
+ def initialize(color, board)
12
+ @color = color
13
+ @board = board
14
+ @history = []
15
+ end
16
+
17
+ private
18
+
19
+ def maximizing_player?
20
+ color == :white
21
+ end
22
+ end
23
+
24
+ class Computer < Player
25
+ include AI
26
+
27
+ # depth: Levels of AI#minimax recursion.
28
+ # Deeper means harder (computer can think `depth` turns ahead)
29
+ # See Engine#set_difficulty
30
+ attr_accessor :depth
31
+
32
+ def select_move
33
+ computer_chooses_move
34
+ end
35
+ end
36
+
37
+ class Human < Player
38
+ include AlgebraicConversion
39
+
40
+ def select_move
41
+ algebraic_input
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module SapphireChess
2
+ VERSION ||= '1.0.0'
3
+ end