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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +80 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/just_shogi.gemspec +29 -0
- data/lib/just_shogi.rb +8 -0
- data/lib/just_shogi/errors/causes_check_error.rb +22 -0
- data/lib/just_shogi/errors/dropped_into_check_error.rb +22 -0
- data/lib/just_shogi/errors/error.rb +20 -0
- data/lib/just_shogi/errors/invalid_move_error.rb +22 -0
- data/lib/just_shogi/errors/invalid_promotion_error.rb +22 -0
- data/lib/just_shogi/errors/moved_into_check_error.rb +22 -0
- data/lib/just_shogi/errors/no_piece_error.rb +22 -0
- data/lib/just_shogi/errors/not_players_turn_error.rb +22 -0
- data/lib/just_shogi/errors/off_board_error.rb +22 -0
- data/lib/just_shogi/errors/piece_not_found_error.rb +22 -0
- data/lib/just_shogi/errors/square_occupied_error.rb +22 -0
- data/lib/just_shogi/game_state.rb +355 -0
- data/lib/just_shogi/hand.rb +48 -0
- data/lib/just_shogi/piece_factory.rb +87 -0
- data/lib/just_shogi/pieces/fuhyou.rb +23 -0
- data/lib/just_shogi/pieces/ginshou.rb +23 -0
- data/lib/just_shogi/pieces/gyokushou.rb +9 -0
- data/lib/just_shogi/pieces/hisha.rb +23 -0
- data/lib/just_shogi/pieces/kakugyou.rb +23 -0
- data/lib/just_shogi/pieces/keima.rb +23 -0
- data/lib/just_shogi/pieces/kin_base.rb +23 -0
- data/lib/just_shogi/pieces/kinshou.rb +9 -0
- data/lib/just_shogi/pieces/kyousha.rb +23 -0
- data/lib/just_shogi/pieces/narigin.rb +9 -0
- data/lib/just_shogi/pieces/narikei.rb +9 -0
- data/lib/just_shogi/pieces/narikyou.rb +9 -0
- data/lib/just_shogi/pieces/ou_base.rb +70 -0
- data/lib/just_shogi/pieces/oushou.rb +9 -0
- data/lib/just_shogi/pieces/piece.rb +18 -0
- data/lib/just_shogi/pieces/ryuuma.rb +23 -0
- data/lib/just_shogi/pieces/ryuuou.rb +23 -0
- data/lib/just_shogi/pieces/tokin.rb +9 -0
- data/lib/just_shogi/promotion_factory.rb +71 -0
- data/lib/just_shogi/square.rb +51 -0
- data/lib/just_shogi/square_set.rb +64 -0
- data/lib/just_shogi/version.rb +3 -0
- 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,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,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,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
|