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,22 @@
|
|
1
|
+
require 'just_shogi/errors/error'
|
2
|
+
|
3
|
+
module JustShogi
|
4
|
+
|
5
|
+
# = NoPieceError
|
6
|
+
#
|
7
|
+
# A no piece error with a message
|
8
|
+
class NoPieceError < Error
|
9
|
+
|
10
|
+
# New no piece errors can be instantiated with
|
11
|
+
#
|
12
|
+
# @option [String] message
|
13
|
+
# the message to display.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new NoPieceError
|
17
|
+
# JustShogi::NoPieceError.new("Custom Message")
|
18
|
+
def initialize(message="There is no piece to move.")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'just_shogi/errors/error'
|
2
|
+
|
3
|
+
module JustShogi
|
4
|
+
|
5
|
+
# = NotPlayersTurnError
|
6
|
+
#
|
7
|
+
# A not players turn error with a message
|
8
|
+
class NotPlayersTurnError < Error
|
9
|
+
|
10
|
+
# New not players turn errors can be instantiated with
|
11
|
+
#
|
12
|
+
# @option [String] message
|
13
|
+
# the message to display.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Intantiates a new NotPlayersTurnError
|
17
|
+
# JustShogi::NotPlayersTurnError.new("Custom Message")
|
18
|
+
def initialize(message="It is not the player's turn yet.")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'just_shogi/errors/error'
|
2
|
+
|
3
|
+
module JustShogi
|
4
|
+
|
5
|
+
# = OffBoardError
|
6
|
+
#
|
7
|
+
# An off board error with a message
|
8
|
+
class OffBoardError < Error
|
9
|
+
|
10
|
+
# New off board errors can be instantiated with
|
11
|
+
#
|
12
|
+
# @option [String] message
|
13
|
+
# the message to display.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new OffBoardError
|
17
|
+
# JustShogi::OffBoardError.new("Custom Message")
|
18
|
+
def initialize(message="Cannot move off board.")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'just_shogi/errors/error'
|
2
|
+
|
3
|
+
module JustShogi
|
4
|
+
|
5
|
+
# = PieceNotFoundError
|
6
|
+
#
|
7
|
+
# An piece not found error with a message
|
8
|
+
class PieceNotFoundError < Error
|
9
|
+
|
10
|
+
# New piece not found errors can be instantiated with
|
11
|
+
#
|
12
|
+
# @option [String] message
|
13
|
+
# the message to display.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new PieceNotFoundError
|
17
|
+
# JustShogi::PieceNotFoundError.new("Custom Message")
|
18
|
+
def initialize(message="The piece could not be found.")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'just_shogi/errors/error'
|
2
|
+
|
3
|
+
module JustShogi
|
4
|
+
|
5
|
+
# = SquareOccupiedError
|
6
|
+
#
|
7
|
+
# A square occupied error with a message
|
8
|
+
class SquareOccupiedError < Error
|
9
|
+
|
10
|
+
# New invalid promotion errors can be instantiated with
|
11
|
+
#
|
12
|
+
# @option [String] message
|
13
|
+
# the message to display.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
# # Instantiates a new SquareOccupiedError
|
17
|
+
# JustShogi::SquareOccupiedError.new("Custom Message")
|
18
|
+
def initialize(message="Square is already occupied.")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,355 @@
|
|
1
|
+
require 'just_shogi/errors/no_piece_error'
|
2
|
+
require 'just_shogi/errors/not_players_turn_error'
|
3
|
+
require 'just_shogi/errors/off_board_error'
|
4
|
+
require 'just_shogi/errors/invalid_move_error'
|
5
|
+
require 'just_shogi/errors/moved_into_check_error'
|
6
|
+
require 'just_shogi/errors/invalid_promotion_error'
|
7
|
+
require 'just_shogi/errors/piece_not_found_error'
|
8
|
+
require 'just_shogi/errors/square_occupied_error'
|
9
|
+
require 'just_shogi/errors/dropped_into_check_error'
|
10
|
+
require 'just_shogi/square_set'
|
11
|
+
require 'just_shogi/hand'
|
12
|
+
require 'just_shogi/promotion_factory'
|
13
|
+
|
14
|
+
module JustShogi
|
15
|
+
|
16
|
+
# = Game State
|
17
|
+
#
|
18
|
+
# Represents a game of Shogi in progress.
|
19
|
+
class GameState
|
20
|
+
def initialize(current_player_number: , squares: [], hands: [])
|
21
|
+
@current_player_number = current_player_number
|
22
|
+
@squares = if squares.is_a?(SquareSet)
|
23
|
+
squares
|
24
|
+
else
|
25
|
+
SquareSet.new(squares: squares)
|
26
|
+
end
|
27
|
+
@hands = if hands.is_a?(Array)
|
28
|
+
if hands.all? { |hand| hand.is_a?(Hand) }
|
29
|
+
hands
|
30
|
+
elsif hands.all? { |hand| hand.is_a?(Hash) }
|
31
|
+
hands.map { |hand| Hand.new(**hand) }
|
32
|
+
else
|
33
|
+
raise ArgumentError, "hands must all be the same class"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise ArgumentError, "hands must be an array"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :current_player_number, :squares, :hands, :errors
|
41
|
+
|
42
|
+
# Instantiates a new GameState object in the starting position.
|
43
|
+
#
|
44
|
+
# @return [GameState]
|
45
|
+
def self.default
|
46
|
+
new(
|
47
|
+
current_player_number: 1,
|
48
|
+
squares: [
|
49
|
+
{ id: '91', x: 0, y: 0, piece: { id: 1, player_number: 2, type: 'kyousha' } },
|
50
|
+
{ id: '81', x: 1, y: 0, piece: { id: 2, player_number: 2, type: 'keima' } },
|
51
|
+
{ id: '71', x: 2, y: 0, piece: { id: 3, player_number: 2, type: 'ginshou' } },
|
52
|
+
{ id: '61', x: 3, y: 0, piece: { id: 4, player_number: 2, type: 'kinshou' } },
|
53
|
+
{ id: '51', x: 4, y: 0, piece: { id: 5, player_number: 2, type: 'oushou' } },
|
54
|
+
{ id: '41', x: 5, y: 0, piece: { id: 6, player_number: 2, type: 'kinshou' } },
|
55
|
+
{ id: '31', x: 6, y: 0, piece: { id: 7, player_number: 2, type: 'ginshou' } },
|
56
|
+
{ id: '21', x: 7, y: 0, piece: { id: 8, player_number: 2, type: 'keima' } },
|
57
|
+
{ id: '11', x: 8, y: 0, piece: { id: 9, player_number: 2, type: 'kyousha' } },
|
58
|
+
|
59
|
+
{ id: '92', x: 0, y: 1, piece: nil },
|
60
|
+
{ id: '82', x: 1, y: 1, piece: { id: 10, player_number: 2, type: 'hisha' } },
|
61
|
+
{ id: '72', x: 2, y: 1, piece: nil },
|
62
|
+
{ id: '62', x: 3, y: 1, piece: nil },
|
63
|
+
{ id: '52', x: 4, y: 1, piece: nil },
|
64
|
+
{ id: '42', x: 5, y: 1, piece: nil },
|
65
|
+
{ id: '32', x: 6, y: 1, piece: nil },
|
66
|
+
{ id: '22', x: 7, y: 1, piece: { id: 11, player_number: 2, type: 'kakugyou' } },
|
67
|
+
{ id: '12', x: 8, y: 1, piece: nil },
|
68
|
+
|
69
|
+
{ id: '93', x: 0, y: 2, piece: { id: 12, player_number: 2, type: 'fuhyou' } },
|
70
|
+
{ id: '83', x: 1, y: 2, piece: { id: 13, player_number: 2, type: 'fuhyou' } },
|
71
|
+
{ id: '73', x: 2, y: 2, piece: { id: 14, player_number: 2, type: 'fuhyou' } },
|
72
|
+
{ id: '63', x: 3, y: 2, piece: { id: 15, player_number: 2, type: 'fuhyou' } },
|
73
|
+
{ id: '53', x: 4, y: 2, piece: { id: 16, player_number: 2, type: 'fuhyou' } },
|
74
|
+
{ id: '43', x: 5, y: 2, piece: { id: 17, player_number: 2, type: 'fuhyou' } },
|
75
|
+
{ id: '33', x: 6, y: 2, piece: { id: 18, player_number: 2, type: 'fuhyou' } },
|
76
|
+
{ id: '23', x: 7, y: 2, piece: { id: 19, player_number: 2, type: 'fuhyou' } },
|
77
|
+
{ id: '13', x: 8, y: 2, piece: { id: 20, player_number: 2, type: 'fuhyou' } },
|
78
|
+
|
79
|
+
{ id: '94', x: 0, y: 3, piece: nil },
|
80
|
+
{ id: '84', x: 1, y: 3, piece: nil },
|
81
|
+
{ id: '74', x: 2, y: 3, piece: nil },
|
82
|
+
{ id: '64', x: 3, y: 3, piece: nil },
|
83
|
+
{ id: '54', x: 4, y: 3, piece: nil },
|
84
|
+
{ id: '44', x: 5, y: 3, piece: nil },
|
85
|
+
{ id: '34', x: 6, y: 3, piece: nil },
|
86
|
+
{ id: '24', x: 7, y: 3, piece: nil },
|
87
|
+
{ id: '14', x: 8, y: 3, piece: nil },
|
88
|
+
|
89
|
+
{ id: '95', x: 0, y: 4, piece: nil },
|
90
|
+
{ id: '85', x: 1, y: 4, piece: nil },
|
91
|
+
{ id: '75', x: 2, y: 4, piece: nil },
|
92
|
+
{ id: '65', x: 3, y: 4, piece: nil },
|
93
|
+
{ id: '55', x: 4, y: 4, piece: nil },
|
94
|
+
{ id: '45', x: 5, y: 4, piece: nil },
|
95
|
+
{ id: '35', x: 6, y: 4, piece: nil },
|
96
|
+
{ id: '25', x: 7, y: 4, piece: nil },
|
97
|
+
{ id: '15', x: 8, y: 4, piece: nil },
|
98
|
+
|
99
|
+
{ id: '96', x: 0, y: 5, piece: nil },
|
100
|
+
{ id: '86', x: 1, y: 5, piece: nil },
|
101
|
+
{ id: '76', x: 2, y: 5, piece: nil },
|
102
|
+
{ id: '66', x: 3, y: 5, piece: nil },
|
103
|
+
{ id: '56', x: 4, y: 5, piece: nil },
|
104
|
+
{ id: '46', x: 5, y: 5, piece: nil },
|
105
|
+
{ id: '36', x: 6, y: 5, piece: nil },
|
106
|
+
{ id: '26', x: 7, y: 5, piece: nil },
|
107
|
+
{ id: '16', x: 8, y: 5, piece: nil },
|
108
|
+
|
109
|
+
{ id: '97', x: 0, y: 6, piece: { id: 21, player_number: 1, type: 'fuhyou' } },
|
110
|
+
{ id: '87', x: 1, y: 6, piece: { id: 22, player_number: 1, type: 'fuhyou' } },
|
111
|
+
{ id: '77', x: 2, y: 6, piece: { id: 23, player_number: 1, type: 'fuhyou' } },
|
112
|
+
{ id: '67', x: 3, y: 6, piece: { id: 24, player_number: 1, type: 'fuhyou' } },
|
113
|
+
{ id: '57', x: 4, y: 6, piece: { id: 25, player_number: 1, type: 'fuhyou' } },
|
114
|
+
{ id: '47', x: 5, y: 6, piece: { id: 26, player_number: 1, type: 'fuhyou' } },
|
115
|
+
{ id: '37', x: 6, y: 6, piece: { id: 27, player_number: 1, type: 'fuhyou' } },
|
116
|
+
{ id: '27', x: 7, y: 6, piece: { id: 28, player_number: 1, type: 'fuhyou' } },
|
117
|
+
{ id: '17', x: 8, y: 6, piece: { id: 29, player_number: 1, type: 'fuhyou' } },
|
118
|
+
|
119
|
+
{ id: '98', x: 0, y: 7, piece: nil },
|
120
|
+
{ id: '88', x: 1, y: 7, piece: { id: 30, player_number: 1, type: 'kakugyou' } },
|
121
|
+
{ id: '78', x: 2, y: 7, piece: nil },
|
122
|
+
{ id: '68', x: 3, y: 7, piece: nil },
|
123
|
+
{ id: '58', x: 4, y: 7, piece: nil },
|
124
|
+
{ id: '48', x: 5, y: 7, piece: nil },
|
125
|
+
{ id: '38', x: 6, y: 7, piece: nil },
|
126
|
+
{ id: '28', x: 7, y: 7, piece: { id: 31, player_number: 1, type: 'hisha' } },
|
127
|
+
{ id: '18', x: 8, y: 7, piece: nil },
|
128
|
+
|
129
|
+
{ id: '99', x: 0, y: 8, piece: { id: 32, player_number: 1, type: 'kyousha' } },
|
130
|
+
{ id: '89', x: 1, y: 8, piece: { id: 33, player_number: 1, type: 'keima' } },
|
131
|
+
{ id: '79', x: 2, y: 8, piece: { id: 34, player_number: 1, type: 'ginshou' } },
|
132
|
+
{ id: '69', x: 3, y: 8, piece: { id: 35, player_number: 1, type: 'kinshou' } },
|
133
|
+
{ id: '59', x: 4, y: 8, piece: { id: 36, player_number: 1, type: 'gyokushou' } },
|
134
|
+
{ id: '49', x: 5, y: 8, piece: { id: 37, player_number: 1, type: 'kinshou' } },
|
135
|
+
{ id: '39', x: 6, y: 8, piece: { id: 38, player_number: 1, type: 'ginshou' } },
|
136
|
+
{ id: '29', x: 7, y: 8, piece: { id: 39, player_number: 1, type: 'keima' } },
|
137
|
+
{ id: '19', x: 8, y: 8, piece: { id: 40, player_number: 1, type: 'kyousha' } }
|
138
|
+
],
|
139
|
+
hands: [
|
140
|
+
{ player_number: 1, pieces: [] },
|
141
|
+
{ player_number: 2, pieces: [] },
|
142
|
+
]
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
# serializes the game state as ahash
|
147
|
+
#
|
148
|
+
# @return [Hash]
|
149
|
+
def as_json
|
150
|
+
{
|
151
|
+
current_player_number: current_player_number,
|
152
|
+
squares: squares.as_json,
|
153
|
+
hands: hands.map(&:as_json)
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
# deep clone of the game state
|
158
|
+
#
|
159
|
+
# @return [GameState]
|
160
|
+
def clone
|
161
|
+
self.class.new(**as_json)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Moves a piece owned by the player, from one square, to another.
|
165
|
+
#
|
166
|
+
# It has an option to promote the moving piece.
|
167
|
+
# It moves the piece and returns true if the move is valid and it's the player's turn.
|
168
|
+
# It returns false otherwise.
|
169
|
+
#
|
170
|
+
# ==== Example:
|
171
|
+
# # Moves a piece from a square to perform a move
|
172
|
+
# game_state.move(1, '77', '78')
|
173
|
+
#
|
174
|
+
# @param [Fixnum] player_number
|
175
|
+
# the player number, 1 or 2.
|
176
|
+
#
|
177
|
+
# @param [String] from_id
|
178
|
+
# the id of the from square
|
179
|
+
#
|
180
|
+
# @param [String] to_id
|
181
|
+
# the id of the to square
|
182
|
+
#
|
183
|
+
# @param [boolean] promote
|
184
|
+
#
|
185
|
+
# @return [Boolean]
|
186
|
+
def move(player_number, from_id, to_id, promote = false)
|
187
|
+
@errors = []
|
188
|
+
|
189
|
+
from = squares.find_by_id(from_id)
|
190
|
+
to = squares.find_by_id(to_id)
|
191
|
+
|
192
|
+
if current_player_number != player_number
|
193
|
+
@errors.push JustShogi::NotPlayersTurnError.new
|
194
|
+
elsif from.unoccupied?
|
195
|
+
@errors.push JustShogi::NoPieceError.new
|
196
|
+
elsif to.nil?
|
197
|
+
@errors.push JustShogi::OffBoardError.new
|
198
|
+
elsif promote && !promotable?(player_number, from, to)
|
199
|
+
@errors.push JustShogi::InvalidPromotionError.new
|
200
|
+
elsif from.piece.can_move?(from, to, self)
|
201
|
+
|
202
|
+
duplicate = self.clone
|
203
|
+
duplicate.perform_complete_move(player_number, from_id, to_id, promote)
|
204
|
+
|
205
|
+
if duplicate.in_check?(current_player_number)
|
206
|
+
@errors.push JustShogi::MovedIntoCheckError.new
|
207
|
+
else
|
208
|
+
perform_complete_move(player_number, from_id, to_id, promote)
|
209
|
+
end
|
210
|
+
else
|
211
|
+
@errors.push JustShogi::InvalidMoveError.new
|
212
|
+
end
|
213
|
+
|
214
|
+
@errors.empty?
|
215
|
+
end
|
216
|
+
|
217
|
+
def drop(player_number, piece_id, square_id)
|
218
|
+
@errors = []
|
219
|
+
|
220
|
+
piece = hand_for_player(player_number).find_piece_by_id(piece_id)
|
221
|
+
square = squares.find_by_id(square_id)
|
222
|
+
|
223
|
+
if current_player_number != player_number
|
224
|
+
@errors.push JustShogi::NotPlayersTurnError.new
|
225
|
+
elsif piece.nil?
|
226
|
+
@errors.push JustShogi::PieceNotFoundError.new
|
227
|
+
elsif square.nil?
|
228
|
+
@errors.push JustShogi::OffBoardError.new
|
229
|
+
elsif square.occupied?
|
230
|
+
@errors.push JustShogi::SquareOccupiedError.new
|
231
|
+
else
|
232
|
+
duplicate = self.clone
|
233
|
+
duplicate.perform_complete_drop(player_number, piece_id, square_id)
|
234
|
+
|
235
|
+
if duplicate.in_check?(opposing_player_number)
|
236
|
+
@errors.push JustShogi::DroppedIntoCheckError.new
|
237
|
+
else
|
238
|
+
perform_complete_drop(player_number, piece_id, square_id)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
@errors.empty?
|
243
|
+
end
|
244
|
+
|
245
|
+
# The player number of the winner. It returns nil if there is no winner.
|
246
|
+
#
|
247
|
+
# @return [Fixnum,NilClass]
|
248
|
+
def winner
|
249
|
+
case
|
250
|
+
when in_checkmate?(1)
|
251
|
+
2
|
252
|
+
when in_checkmate?(2)
|
253
|
+
1
|
254
|
+
else
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Moves a piece owned by the player, from one square, to another, with the option to promote.
|
260
|
+
#
|
261
|
+
# It moves the piece and returns true if the move is valid and it's the player's turn.
|
262
|
+
# It returns false otherwise.
|
263
|
+
# It promotes if possible and specified.
|
264
|
+
#
|
265
|
+
def perform_complete_move(player_number, from_id, to_id, promote = false)
|
266
|
+
from = squares.find_by_id(from_id)
|
267
|
+
to = squares.find_by_id(to_id)
|
268
|
+
|
269
|
+
captured = to.occupied? ? to : nil
|
270
|
+
|
271
|
+
@last_change = { type: 'move', data: { player_number: player_number, from: from_id, to: to_id } }
|
272
|
+
|
273
|
+
perform_move(player_number, from, to, captured)
|
274
|
+
|
275
|
+
promote_piece(to) if promote
|
276
|
+
pass_turn
|
277
|
+
end
|
278
|
+
|
279
|
+
def perform_complete_drop(player_number, piece_id, square_id)
|
280
|
+
hand = hand_for_player(player_number)
|
281
|
+
square = squares.find_by_id(square_id)
|
282
|
+
|
283
|
+
@last_change = { type: 'drop', data: { player_number: player_number, piece: piece_id, square: square_id } }
|
284
|
+
|
285
|
+
piece = hand.pop_piece(piece_id)
|
286
|
+
square.piece = piece
|
287
|
+
|
288
|
+
pass_turn
|
289
|
+
end
|
290
|
+
|
291
|
+
def in_check?(player_number)
|
292
|
+
ou_square = squares.find_ou_for_player(player_number)
|
293
|
+
threatened_by = squares.threatened_by(opposing_player_number(player_number), self)
|
294
|
+
threatened_by.include?(ou_square)
|
295
|
+
end
|
296
|
+
|
297
|
+
def in_checkmate?(player_number)
|
298
|
+
(in_check?(player_number) || non_ou_pieces_cannot_move?(player_number)) && ou_cannot_move?(player_number)
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
def non_ou_pieces_cannot_move?(player_number)
|
304
|
+
squares.occupied_by_player(player_number).excluding_piece(JustShogi::OuBase).all? { |s| s.piece.destinations(s, self).empty? }
|
305
|
+
end
|
306
|
+
|
307
|
+
def ou_cannot_move?(player_number)
|
308
|
+
ou_square = squares.find_ou_for_player(player_number)
|
309
|
+
destinations = ou_square.piece.destinations(ou_square, self)
|
310
|
+
destinations.all? do |destination|
|
311
|
+
duplicate = self.clone
|
312
|
+
duplicate.perform_complete_move(player_number, ou_square.id, destination.id)
|
313
|
+
duplicate.in_check?(player_number)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def pass_turn
|
318
|
+
@current_player_number = opposing_player_number
|
319
|
+
end
|
320
|
+
|
321
|
+
def opposing_player_number(player_number = nil)
|
322
|
+
if player_number
|
323
|
+
other_player_number(player_number)
|
324
|
+
else
|
325
|
+
other_player_number(@current_player_number)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def other_player_number(player_number)
|
330
|
+
(player_number == 1) ? 2 : 1
|
331
|
+
end
|
332
|
+
|
333
|
+
def hand_for_player(player_number)
|
334
|
+
hands.find { |h| h.player_number == player_number }
|
335
|
+
end
|
336
|
+
|
337
|
+
def perform_move(player_number, from, to, captured)
|
338
|
+
if captured
|
339
|
+
hand_for_player(player_number).push_piece(to.piece)
|
340
|
+
captured.piece = nil
|
341
|
+
end
|
342
|
+
to.piece = from.piece
|
343
|
+
from.piece = nil
|
344
|
+
end
|
345
|
+
|
346
|
+
def promotable?(player_number, from, to)
|
347
|
+
PromotionFactory.new(from.piece).promotable? && to.promotion_zone(player_number)
|
348
|
+
end
|
349
|
+
|
350
|
+
def promote_piece(square)
|
351
|
+
new_piece = PromotionFactory.new(square.piece).promote
|
352
|
+
square.piece = new_piece
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|