just_chess 0.1.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/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +74 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/just_chess.gemspec +29 -0
- data/lib/just_chess.rb +8 -0
- data/lib/just_chess/direction.rb +35 -0
- data/lib/just_chess/errors/causes_check_error.rb +22 -0
- data/lib/just_chess/errors/error.rb +20 -0
- data/lib/just_chess/errors/invalid_move_error.rb +22 -0
- data/lib/just_chess/errors/invalid_promotion_error.rb +22 -0
- data/lib/just_chess/errors/moved_into_check_error.rb +22 -0
- data/lib/just_chess/errors/no_piece_error.rb +22 -0
- data/lib/just_chess/errors/not_players_turn_error.rb +22 -0
- data/lib/just_chess/errors/off_board_error.rb +22 -0
- data/lib/just_chess/game_state.rb +323 -0
- data/lib/just_chess/piece_factory.rb +68 -0
- data/lib/just_chess/pieces/bishop.rb +23 -0
- data/lib/just_chess/pieces/king.rb +97 -0
- data/lib/just_chess/pieces/knight.rb +23 -0
- data/lib/just_chess/pieces/pawn.rb +103 -0
- data/lib/just_chess/pieces/piece.rb +121 -0
- data/lib/just_chess/pieces/queen.rb +23 -0
- data/lib/just_chess/pieces/rook.rb +23 -0
- data/lib/just_chess/point.rb +52 -0
- data/lib/just_chess/square.rb +124 -0
- data/lib/just_chess/square_set.rb +407 -0
- data/lib/just_chess/vector.rb +86 -0
- data/lib/just_chess/version.rb +4 -0
- metadata +120 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'just_chess/pieces/piece'
|
2
|
+
|
3
|
+
module JustChess
|
4
|
+
|
5
|
+
# = King
|
6
|
+
#
|
7
|
+
# The piece that moves 1 space away. Can castle and must not be put into check.
|
8
|
+
class King < Piece
|
9
|
+
|
10
|
+
# All the squares that the piece can move to and/or capture.
|
11
|
+
#
|
12
|
+
# @param [Square] square
|
13
|
+
# the origin square.
|
14
|
+
#
|
15
|
+
# @param [GameState] game_state
|
16
|
+
# the current game state.
|
17
|
+
#
|
18
|
+
# @return [SquareSet]
|
19
|
+
def destinations(square, game_state)
|
20
|
+
base_destinations(square, game_state) + castle(square, game_state) - checked_squares(square, game_state) - shared_king_squares(game_state)
|
21
|
+
end
|
22
|
+
|
23
|
+
# All the squares that the king could move to normally.
|
24
|
+
#
|
25
|
+
# @param [Square] square
|
26
|
+
# the origin square.
|
27
|
+
#
|
28
|
+
# @param [GameState] game_state
|
29
|
+
# the current game state.
|
30
|
+
#
|
31
|
+
# @return [SquareSet]
|
32
|
+
def base_destinations(square, game_state)
|
33
|
+
game_state.squares.at_range(square, 1).unoccupied_or_occupied_by_opponent(player_number)
|
34
|
+
end
|
35
|
+
|
36
|
+
# All the squares that the king could castle to.
|
37
|
+
#
|
38
|
+
# @param [Square] square
|
39
|
+
# the origin square.
|
40
|
+
#
|
41
|
+
# @param [GameState] game_state
|
42
|
+
# the current game state.
|
43
|
+
#
|
44
|
+
# @return [SquareSet]
|
45
|
+
def castle(square, game_state)
|
46
|
+
rooks = game_state.squares.occupied_by_piece(Rook).occupied_by_player(player_number).unmoved()
|
47
|
+
|
48
|
+
if has_not_moved? && rooks.any?
|
49
|
+
_squares = rooks.map do |s|
|
50
|
+
vector = Vector.new(square, s)
|
51
|
+
x = square.x + (2 * vector.direction.x)
|
52
|
+
y = square.y
|
53
|
+
game_state.squares.find_by_x_and_y(x, y)
|
54
|
+
end
|
55
|
+
|
56
|
+
potential = SquareSet.new(squares: _squares)
|
57
|
+
|
58
|
+
potential.unoccupied().unblocked(square, game_state.squares)
|
59
|
+
else
|
60
|
+
SquareSet.new(squares: [])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# All the squares that the king could not move to because of check.
|
65
|
+
#
|
66
|
+
# @param [Square] square
|
67
|
+
# the origin square.
|
68
|
+
#
|
69
|
+
# @param [GameState] game_state
|
70
|
+
# the current game state.
|
71
|
+
#
|
72
|
+
# @return [SquareSet]
|
73
|
+
def checked_squares(square, game_state)
|
74
|
+
dup = game_state.clone
|
75
|
+
dup.squares.find_king_for_player(player_number).piece = nil
|
76
|
+
dup.squares.threatened_by(opponent, dup)
|
77
|
+
end
|
78
|
+
|
79
|
+
# All the squares that the king could not move to because another king is nearby.
|
80
|
+
#
|
81
|
+
# @param [GameState] game_state
|
82
|
+
# the current game state.
|
83
|
+
#
|
84
|
+
# @return [SquareSet]
|
85
|
+
def shared_king_squares(game_state)
|
86
|
+
all = game_state.squares.occupied_by_piece(King).map { |s| s.piece.base_destinations(s, game_state) }
|
87
|
+
|
88
|
+
all.reduce(nil) do |memo, set|
|
89
|
+
if memo
|
90
|
+
memo & set
|
91
|
+
else
|
92
|
+
set
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'just_chess/pieces/piece'
|
2
|
+
|
3
|
+
module JustChess
|
4
|
+
|
5
|
+
# = Knight
|
6
|
+
#
|
7
|
+
# The piece that jumps over pieces 1v2h or 2v1h
|
8
|
+
class Knight < Piece
|
9
|
+
|
10
|
+
# All the squares that the piece can move to and/or capture.
|
11
|
+
#
|
12
|
+
# @param [Square] square
|
13
|
+
# the origin square.
|
14
|
+
#
|
15
|
+
# @param [GameState] game_state
|
16
|
+
# the current game state.
|
17
|
+
#
|
18
|
+
# @return [SquareSet]
|
19
|
+
def destinations(square, game_state)
|
20
|
+
game_state.squares.not_orthogonal_or_diagonal(square).at_range(square,2).unoccupied_or_occupied_by_opponent(player_number)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'just_chess/pieces/piece'
|
2
|
+
require 'just_chess/vector.rb'
|
3
|
+
|
4
|
+
module JustChess
|
5
|
+
|
6
|
+
# = Pawn
|
7
|
+
#
|
8
|
+
# The piece that moves 1 forward or 2 forwards the first time, or 1 diagonal to capture.
|
9
|
+
class Pawn < Piece
|
10
|
+
|
11
|
+
# The forwards direction of the pawn for each player.
|
12
|
+
FORWARDS_DIRECTION = { 1 => -1, 2 => 1 }
|
13
|
+
|
14
|
+
# THe starting rank of the pawn for each player
|
15
|
+
STARTING_RANK = { 1 => 6, 2 => 1 }
|
16
|
+
|
17
|
+
# All the squares that the piece can move to and/or capture.
|
18
|
+
#
|
19
|
+
# @param [Square] square
|
20
|
+
# the origin square.
|
21
|
+
#
|
22
|
+
# @param [GameState] game_state
|
23
|
+
# the current game state.
|
24
|
+
#
|
25
|
+
# @return [SquareSet]
|
26
|
+
def destinations(square, game_state)
|
27
|
+
moves = move_squares(square, game_state)
|
28
|
+
captures = capture_squares(square, game_state)
|
29
|
+
en_passant = en_passant_square(square, game_state)
|
30
|
+
|
31
|
+
if en_passant
|
32
|
+
(moves + captures) << en_passant
|
33
|
+
else
|
34
|
+
moves + captures
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# All the squares that the piece can move to.
|
39
|
+
#
|
40
|
+
# @param [Square] square
|
41
|
+
# the origin square.
|
42
|
+
#
|
43
|
+
# @param [GameState] game_state
|
44
|
+
# the current game state.
|
45
|
+
#
|
46
|
+
# @return [SquareSet]
|
47
|
+
def move_squares(square, game_state)
|
48
|
+
game_state.squares.in_range(square, range(square)).in_direction(square, forwards_direction).orthogonal(square).unoccupied.unblocked(square, game_state.squares)
|
49
|
+
end
|
50
|
+
|
51
|
+
# All the squares that the piece can capture.
|
52
|
+
#
|
53
|
+
# @param [Square] square
|
54
|
+
# the origin square.
|
55
|
+
#
|
56
|
+
# @param [GameState] game_state
|
57
|
+
# the current game state.
|
58
|
+
#
|
59
|
+
# @return [SquareSet]
|
60
|
+
def capture_squares(square, game_state)
|
61
|
+
game_state.squares.in_range(square, 1).in_direction(square, forwards_direction).diagonal(square).occupied_by_opponent(player_number)
|
62
|
+
end
|
63
|
+
|
64
|
+
# The Square that a pawn can capture en passant.
|
65
|
+
#
|
66
|
+
# @param [Square] square
|
67
|
+
# the origin square.
|
68
|
+
#
|
69
|
+
# @param [GameState] game_state
|
70
|
+
# the current game state.
|
71
|
+
#
|
72
|
+
# @return [Square]
|
73
|
+
def en_passant_square(square, game_state)
|
74
|
+
if square.rank_number(player_number) == 5 && game_state.last_double_step_pawn_id
|
75
|
+
double_step = game_state.squares.find_by_piece_id(game_state.last_double_step_pawn_id)
|
76
|
+
vector = Vector.new(square, double_step)
|
77
|
+
if vector.magnitude.abs == 1
|
78
|
+
x = double_step.x
|
79
|
+
y = square.y + forwards_direction
|
80
|
+
game_state.squares.find_by_x_and_y(x, y)
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
else
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def range(from)
|
92
|
+
from.y == starting_rank ? 2 : 1
|
93
|
+
end
|
94
|
+
|
95
|
+
def forwards_direction
|
96
|
+
FORWARDS_DIRECTION[player_number]
|
97
|
+
end
|
98
|
+
|
99
|
+
def starting_rank
|
100
|
+
STARTING_RANK[player_number]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'just_chess/vector'
|
2
|
+
|
3
|
+
module JustChess
|
4
|
+
|
5
|
+
# = Piece
|
6
|
+
#
|
7
|
+
# A piece that can move on a chess board
|
8
|
+
class Piece
|
9
|
+
def initialize(id: , player_number: , type: nil, has_moved: false)
|
10
|
+
@id = id
|
11
|
+
@player_number = player_number
|
12
|
+
@has_moved = has_moved
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Fixnum] the identifier of the piece.
|
16
|
+
attr_reader :id
|
17
|
+
|
18
|
+
# @return [Fixnum] the owner of the piece.
|
19
|
+
attr_reader :player_number
|
20
|
+
|
21
|
+
# @return [Boolean] determines if the piece has moved.
|
22
|
+
attr_reader :has_moved
|
23
|
+
|
24
|
+
alias_method :has_moved?, :has_moved
|
25
|
+
|
26
|
+
# The opposing player number
|
27
|
+
#
|
28
|
+
# @return [Fixnum]
|
29
|
+
def opponent
|
30
|
+
player_number == 1 ? 2 : 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# mark the piece as moved
|
34
|
+
#
|
35
|
+
# @return [TrueClass]
|
36
|
+
def moved
|
37
|
+
@has_moved = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# The stringified identifier of the piece type.
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def has_not_moved?
|
44
|
+
!has_moved?
|
45
|
+
end
|
46
|
+
|
47
|
+
# The stringified identifier of the piece type.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def type
|
51
|
+
self.class.to_s.split('::').last.downcase
|
52
|
+
end
|
53
|
+
|
54
|
+
# Can the piece move from a square, to a square, given the game state?
|
55
|
+
#
|
56
|
+
# @param [Square] from
|
57
|
+
# the origin square.
|
58
|
+
#
|
59
|
+
# @param [Square] to
|
60
|
+
# the destination square.
|
61
|
+
#
|
62
|
+
# @param [GameState] game_state
|
63
|
+
# the current game state.
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
def can_move?(from, to, game_state)
|
67
|
+
destinations(from, game_state).include?(to)
|
68
|
+
end
|
69
|
+
|
70
|
+
# All the squares that the piece can move to and/or capture.
|
71
|
+
#
|
72
|
+
# @param [Square] square
|
73
|
+
# the origin square.
|
74
|
+
#
|
75
|
+
# @param [GameState] game_state
|
76
|
+
# the current game state.
|
77
|
+
#
|
78
|
+
# @return [SquareSet]
|
79
|
+
def destinations(square, game_state)
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
|
83
|
+
# All the squares that the piece can move to.
|
84
|
+
#
|
85
|
+
# @param [Square] square
|
86
|
+
# the origin square.
|
87
|
+
#
|
88
|
+
# @param [GameState] game_state
|
89
|
+
# the current game state.
|
90
|
+
#
|
91
|
+
# @return [SquareSet]
|
92
|
+
def move_squares(square, game_state)
|
93
|
+
destinations(square, game_state)
|
94
|
+
end
|
95
|
+
|
96
|
+
# All the squares that the piece can capture.
|
97
|
+
#
|
98
|
+
# @param [Square] square
|
99
|
+
# the origin square.
|
100
|
+
#
|
101
|
+
# @param [GameState] game_state
|
102
|
+
# the current game state.
|
103
|
+
#
|
104
|
+
# @return [SquareSet]
|
105
|
+
def capture_squares(square, game_state)
|
106
|
+
destinations(square, game_state)
|
107
|
+
end
|
108
|
+
|
109
|
+
# returns a serialized piece as a hash
|
110
|
+
#
|
111
|
+
# @return [Hash]
|
112
|
+
def as_json
|
113
|
+
{
|
114
|
+
id: id,
|
115
|
+
player_number: player_number,
|
116
|
+
type: type,
|
117
|
+
has_moved: has_moved?
|
118
|
+
}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'just_chess/pieces/piece'
|
2
|
+
|
3
|
+
module JustChess
|
4
|
+
|
5
|
+
# = Queen
|
6
|
+
#
|
7
|
+
# The piece that moves any number of squares orthogonally or diagonally.
|
8
|
+
class Queen < Piece
|
9
|
+
|
10
|
+
# All the squares that the piece can move to and/or capture.
|
11
|
+
#
|
12
|
+
# @param [Square] square
|
13
|
+
# the origin square.
|
14
|
+
#
|
15
|
+
# @param [GameState] game_state
|
16
|
+
# the current game state.
|
17
|
+
#
|
18
|
+
# @return [SquareSet]
|
19
|
+
def destinations(square, game_state)
|
20
|
+
game_state.squares.orthogonal_or_diagonal(square).unoccupied_or_occupied_by_opponent(player_number).unblocked(square, game_state.squares)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'just_chess/pieces/piece'
|
2
|
+
|
3
|
+
module JustChess
|
4
|
+
|
5
|
+
# = Rook
|
6
|
+
#
|
7
|
+
# The piece that moves any number of squares orthogonally.
|
8
|
+
class Rook < Piece
|
9
|
+
|
10
|
+
# All the squares that the piece can move to and/or capture.
|
11
|
+
#
|
12
|
+
# @param [Square] square
|
13
|
+
# the origin square.
|
14
|
+
#
|
15
|
+
# @param [GameState] game_state
|
16
|
+
# the current game state.
|
17
|
+
#
|
18
|
+
# @return [SquareSet]
|
19
|
+
def destinations(square, game_state)
|
20
|
+
game_state.squares.orthogonal(square).unoccupied_or_occupied_by_opponent(player_number).unblocked(square, game_state.squares)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module JustChess
|
2
|
+
|
3
|
+
# = Point
|
4
|
+
#
|
5
|
+
# A point with an x and y co-ordinates
|
6
|
+
class Point
|
7
|
+
|
8
|
+
# New objects can be instantiated with
|
9
|
+
#
|
10
|
+
# @param [Fixnum] x
|
11
|
+
# the x co-ordinate.
|
12
|
+
#
|
13
|
+
# @param [Fixnum] y
|
14
|
+
# the y co-ordinate.
|
15
|
+
#
|
16
|
+
# ==== Example:
|
17
|
+
# # Instantiates a new Point
|
18
|
+
# JustChess::Point.new({
|
19
|
+
# x: 1,
|
20
|
+
# y: 1
|
21
|
+
# })
|
22
|
+
def initialize(x, y)
|
23
|
+
@x, @y = x, y
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Fixnum] the x co-ordinate.
|
27
|
+
attr_reader :x
|
28
|
+
|
29
|
+
# @return [Fixnum] the y co-ordinate.
|
30
|
+
attr_reader :y
|
31
|
+
|
32
|
+
# Add a point to another point by adding their co-ordinates and returning a new point.
|
33
|
+
#
|
34
|
+
# @param [Point] other
|
35
|
+
# the other point to add.
|
36
|
+
#
|
37
|
+
# @return [Point]
|
38
|
+
def +(other)
|
39
|
+
self.class.new(self.x + other.x, self.y + other.y)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if popints are equal by seeing if their co-ordinates are equal.
|
43
|
+
#
|
44
|
+
# @param [Point] other
|
45
|
+
# the other point to compare to.
|
46
|
+
#
|
47
|
+
# @return [TrueClass, FalseClass]
|
48
|
+
def ==(other)
|
49
|
+
self.x == other.x && self.y == other.y
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|