just_shogi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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