just_shogi 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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +5 -0
  5. data/Gemfile.lock +24 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +80 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/just_shogi.gemspec +29 -0
  12. data/lib/just_shogi.rb +8 -0
  13. data/lib/just_shogi/errors/causes_check_error.rb +22 -0
  14. data/lib/just_shogi/errors/dropped_into_check_error.rb +22 -0
  15. data/lib/just_shogi/errors/error.rb +20 -0
  16. data/lib/just_shogi/errors/invalid_move_error.rb +22 -0
  17. data/lib/just_shogi/errors/invalid_promotion_error.rb +22 -0
  18. data/lib/just_shogi/errors/moved_into_check_error.rb +22 -0
  19. data/lib/just_shogi/errors/no_piece_error.rb +22 -0
  20. data/lib/just_shogi/errors/not_players_turn_error.rb +22 -0
  21. data/lib/just_shogi/errors/off_board_error.rb +22 -0
  22. data/lib/just_shogi/errors/piece_not_found_error.rb +22 -0
  23. data/lib/just_shogi/errors/square_occupied_error.rb +22 -0
  24. data/lib/just_shogi/game_state.rb +355 -0
  25. data/lib/just_shogi/hand.rb +48 -0
  26. data/lib/just_shogi/piece_factory.rb +87 -0
  27. data/lib/just_shogi/pieces/fuhyou.rb +23 -0
  28. data/lib/just_shogi/pieces/ginshou.rb +23 -0
  29. data/lib/just_shogi/pieces/gyokushou.rb +9 -0
  30. data/lib/just_shogi/pieces/hisha.rb +23 -0
  31. data/lib/just_shogi/pieces/kakugyou.rb +23 -0
  32. data/lib/just_shogi/pieces/keima.rb +23 -0
  33. data/lib/just_shogi/pieces/kin_base.rb +23 -0
  34. data/lib/just_shogi/pieces/kinshou.rb +9 -0
  35. data/lib/just_shogi/pieces/kyousha.rb +23 -0
  36. data/lib/just_shogi/pieces/narigin.rb +9 -0
  37. data/lib/just_shogi/pieces/narikei.rb +9 -0
  38. data/lib/just_shogi/pieces/narikyou.rb +9 -0
  39. data/lib/just_shogi/pieces/ou_base.rb +70 -0
  40. data/lib/just_shogi/pieces/oushou.rb +9 -0
  41. data/lib/just_shogi/pieces/piece.rb +18 -0
  42. data/lib/just_shogi/pieces/ryuuma.rb +23 -0
  43. data/lib/just_shogi/pieces/ryuuou.rb +23 -0
  44. data/lib/just_shogi/pieces/tokin.rb +9 -0
  45. data/lib/just_shogi/promotion_factory.rb +71 -0
  46. data/lib/just_shogi/square.rb +51 -0
  47. data/lib/just_shogi/square_set.rb +64 -0
  48. data/lib/just_shogi/version.rb +3 -0
  49. metadata +147 -0
@@ -0,0 +1,48 @@
1
+ require 'just_shogi/piece_factory'
2
+ require 'just_shogi/promotion_factory'
3
+ require 'just_shogi/pieces/piece'
4
+
5
+ module JustShogi
6
+ class Hand
7
+ def initialize(player_number: , pieces: [])
8
+ @player_number = player_number
9
+ @pieces = if pieces.instance_of?(Array)
10
+ if pieces.all? { |p| p.is_a?(Hash) }
11
+ pieces.map { |p| JustShogi::PieceFactory.new(p).build }
12
+ elsif pieces.all? { |p| p.is_a?(JustShogi::Piece) }
13
+ pieces
14
+ else
15
+ raise ArgumentError, "all pieces must have the same class"
16
+ end
17
+ else
18
+ raise ArgumentError, "pieces must be an array"
19
+ end
20
+ end
21
+
22
+ attr_reader :player_number
23
+ attr_reader :pieces
24
+
25
+ def push_piece(piece)
26
+ factory = PromotionFactory.new(piece)
27
+ demoted_piece = factory.demotable? ? factory.demote : piece
28
+ demoted_piece.switch_player
29
+ @pieces.push(demoted_piece)
30
+ end
31
+
32
+ def pop_piece(id)
33
+ p = find_piece_by_id(id)
34
+ @pieces.delete(p)
35
+ end
36
+
37
+ def find_piece_by_id(id)
38
+ pieces.find { |p| p.id == id }
39
+ end
40
+
41
+ def as_json
42
+ {
43
+ player_number: player_number,
44
+ pieces: pieces.map(&:as_json)
45
+ }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,87 @@
1
+ require 'just_shogi/pieces/fuhyou'
2
+ require 'just_shogi/pieces/kakugyou'
3
+ require 'just_shogi/pieces/hisha'
4
+ require 'just_shogi/pieces/kyousha'
5
+ require 'just_shogi/pieces/keima'
6
+ require 'just_shogi/pieces/ginshou'
7
+ require 'just_shogi/pieces/kinshou'
8
+ require 'just_shogi/pieces/oushou'
9
+ require 'just_shogi/pieces/gyokushou'
10
+
11
+ require 'just_shogi/pieces/tokin'
12
+ require 'just_shogi/pieces/narikyou'
13
+ require 'just_shogi/pieces/narikei'
14
+ require 'just_shogi/pieces/narigin'
15
+ require 'just_shogi/pieces/ryuuma'
16
+ require 'just_shogi/pieces/ryuuou'
17
+
18
+ module JustShogi
19
+
20
+ # = Piece Factory
21
+ #
22
+ # Generates pieces from a hash of arguments
23
+ class PieceFactory
24
+
25
+ # A mapping of type descriptions to classes.
26
+ CLASSES = {
27
+ 'fuhyou' => Fuhyou,
28
+ 'kakugyou' => Kakugyou,
29
+ 'hisha' => Hisha,
30
+ 'kyousha' => Kyousha,
31
+ 'keima' => Keima,
32
+ 'ginshou' => Ginshou,
33
+ 'kinshou' => Kinshou,
34
+ 'oushou' => Oushou,
35
+ 'gyokushou' => Gyokushou,
36
+ 'tokin' => Tokin,
37
+ 'narikyou' => Narikyou,
38
+ 'narikei' => Narikei,
39
+ 'narigin' => Narigin,
40
+ 'ryuuma' => Ryuuma,
41
+ 'ryuuou' => Ryuuou
42
+ }
43
+
44
+ # New objects can be instantiated by passing in a hash or the piece.
45
+ #
46
+ # @param [Hash,Piece] args
47
+ # the initial attributes of the piece, hash requires type key
48
+ #
49
+ # ==== Example:
50
+ # # Instantiates a new PieceFactory
51
+ # JustShogi::PieceFactory.new({
52
+ # type: 'fuhyou',
53
+ # id: 1,
54
+ # player_number: 2
55
+ # })
56
+ def initialize(args)
57
+ @args = args
58
+ end
59
+
60
+ # Returns a piece based on the initial arguments.
61
+ #
62
+ # @return [Piece]
63
+ def build
64
+ case @args
65
+ when Hash
66
+ build_from_hash
67
+ when Piece
68
+ @args
69
+ when nil
70
+ nil
71
+ else
72
+ raise ArgumentError, "piece must be Hash or NilClass"
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def build_from_hash
79
+ klass = CLASSES[@args[:type]]
80
+ if klass
81
+ klass.new(**@args)
82
+ else
83
+ raise ArgumentError, "invalid piece type: #{@args[:type].to_s}"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,23 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Fuhyou
6
+ #
7
+ # The piece that moves forward one space.
8
+ class Fuhyou < 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.in_range(square, 1).in_direction(square, forwards_direction).orthogonal(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_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Ginshou
6
+ #
7
+ # The piece that can move 1 space diagonally or forwards orthogonally
8
+ class Ginshou < 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.diagonal(square) | game_state.squares.in_direction(square, forwards_direction)).in_range(square, 1).unoccupied_or_occupied_by_opponent(player_number).unblocked(square, game_state.squares)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/ou_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Gyokushou
6
+ #
7
+ # The piece that moves 1 space away.
8
+ class Gyokushou < OuBase; end
9
+ end
@@ -0,0 +1,23 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Hisha
6
+ #
7
+ # The piece that moves any number of squares orthogonally.
8
+ class Hisha < 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,23 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Kakugyou
6
+ #
7
+ # The piece that moves diagonally any number of squares
8
+ class Kakugyou < 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.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_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Keima
6
+ #
7
+ # The piece that jumps over pieces 2v1h forwards.
8
+ class Keima < Piece
9
+
10
+ # All the suares 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.in_direction(square, forwards_direction).ranks_away(square, 2).files_away(square, 1).unoccupied_or_occupied_by_opponent(player_number)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = KinBase
6
+ #
7
+ # A piece that moves 1 space orthogonally or forwards diagonally
8
+ class KinBase < 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) | game_state.squares.in_direction(square, forwards_direction)).in_range(square, 1).unoccupied_or_occupied_by_opponent(player_number).unblocked(square, game_state.squares)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/kin_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Kinshou
6
+ #
7
+ # The piece that can move 1 space orthogonally or forwards diagonally
8
+ class Kinshou < KinBase; end
9
+ end
@@ -0,0 +1,23 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Kyousha
6
+ #
7
+ # The piece that moves forward any number of spaces.
8
+ class Kyousha < 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).in_direction(square, forwards_direction).unoccupied_or_occupied_by_opponent(player_number).unblocked(square, game_state.squares)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/kin_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Narigin
6
+ #
7
+ # The piece that can move 1 space orthogonally or forwards diagonally
8
+ class Narigin < KinBase; end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/kin_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Narikei
6
+ #
7
+ # The piece that can move 1 space orthogonally or forwards diagonally
8
+ class Narikei < KinBase; end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/kin_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Narikyou
6
+ #
7
+ # The piece that can move 1 space orthogonally or forwards diagonally
8
+ class Narikyou < KinBase; end
9
+ end
@@ -0,0 +1,70 @@
1
+ require 'just_shogi/pieces/piece'
2
+
3
+ module JustShogi
4
+
5
+ # = Ou Base
6
+ #
7
+ # The piece that moves 1 space away.
8
+ class OuBase < 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) # - 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 not move to because of check.
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 checked_squares(square, game_state)
46
+ dup = game_state.clone
47
+ # set the piece to nil to handle case where a piece threatens squares behind this piece.
48
+ dup.squares.find_ou_for_player(player_number).piece = nil
49
+ dup.squares.threatened_by(opponent, dup)
50
+ end
51
+
52
+ # All the squares that the king could not move to because antoher king is nearby.
53
+ #
54
+ # @param [GameState] game_state
55
+ # the current game state.
56
+ #
57
+ # @return [SquareSet]
58
+ def shared_king_squares(game_state)
59
+ all = game_state.squares.occupied_by_piece(JustShogi::OuBase).map { |s| s.piece.base_destinations(s, game_state) }
60
+
61
+ all.reduce(nil) do |memo, set|
62
+ if memo
63
+ memo & set
64
+ else
65
+ set
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,9 @@
1
+ require 'just_shogi/pieces/ou_base'
2
+
3
+ module JustShogi
4
+
5
+ # = Oushou
6
+ #
7
+ # The piece that moves 1 space away.
8
+ class Oushou < OuBase; end
9
+ end