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.
@@ -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