qi 10.0.0.beta9 → 10.0.0.beta11
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 +4 -4
- data/README.md +10 -10
- data/lib/qi/error/drop.rb +5 -0
- data/lib/qi.rb +180 -91
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79ffab5e654d14cf9f3f7dac1c50bc5b5c612452d0fdea35a00d3529337fbff2
|
4
|
+
data.tar.gz: b8fecf9ff5dd97923070809b6a906c8b44cebbee9695ea242e314d53afb9fad8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb36bd477ba3e756b116e3f2ab55bdb15ea50834461cc614865d8fdda56e271eff4675d998172bd766b8ca33e6944be3bbf567a1eba96b6215816234c485f6c9
|
7
|
+
data.tar.gz: e56b5f73bf2c0a1a3a256f813acfcf24af3bc3cdd570f2fb6ab1aa2f7ebc194ddd0b82e5ade5cbc052229377a8c768d3f49f45188d811a756ab7781481c69f08
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
Add this line to your application's Gemfile:
|
14
14
|
|
15
15
|
```ruby
|
16
|
-
gem "qi", ">= 10.0.0.
|
16
|
+
gem "qi", ">= 10.0.0.beta11"
|
17
17
|
```
|
18
18
|
|
19
19
|
And then execute:
|
@@ -45,30 +45,30 @@ qi0.south_captures # => ["S"]
|
|
45
45
|
qi0.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
|
46
46
|
qi0.north_turn? # => false
|
47
47
|
qi0.south_turn? # => true
|
48
|
-
qi0.serialize # => "
|
49
|
-
qi0.inspect # => "<Qi
|
50
|
-
|
48
|
+
qi0.serialize # => "South-turn===b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s,S===3:s,4:k,5:s,22:+P,43:+B"
|
49
|
+
qi0.inspect # => "<Qi South-turn===b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s,S===3:s,4:k,5:s,22:+P,43:+B>"
|
51
50
|
qi0.to_a
|
52
51
|
# [false,
|
53
52
|
# ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"],
|
54
53
|
# ["S"],
|
55
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
|
54
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"},
|
55
|
+
# {}]
|
56
56
|
|
57
|
-
qi1 = qi0.commit(
|
57
|
+
qi1 = qi0.commit(43, 13, "+B", nil)
|
58
58
|
|
59
59
|
qi1.north_captures # => ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"]
|
60
60
|
qi1.south_captures # => ["S"]
|
61
61
|
qi1.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
62
62
|
qi1.north_turn? # => true
|
63
63
|
qi1.south_turn? # => false
|
64
|
-
qi1.serialize # => "
|
65
|
-
qi1.inspect # => "<Qi
|
66
|
-
|
64
|
+
qi1.serialize # => "North-turn===b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s,S===3:s,4:k,5:s,13:+B,22:+P"
|
65
|
+
qi1.inspect # => "<Qi North-turn===b,g,g,g,g,n,n,n,n,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,r,r,s,S===3:s,4:k,5:s,13:+B,22:+P>"
|
67
66
|
qi1.to_a
|
68
67
|
# [true,
|
69
68
|
# ["b", "g", "g", "g", "g", "n", "n", "n", "n", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "r", "r", "s"],
|
70
69
|
# ["S"],
|
71
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
70
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"},
|
71
|
+
# {}]
|
72
72
|
```
|
73
73
|
|
74
74
|
## License
|
data/lib/qi/error/drop.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Qi
|
4
|
+
# The Error module contains custom error classes for the Qi game.
|
4
5
|
module Error
|
6
|
+
# The Drop class is a custom error class that inherits from IndexError.
|
7
|
+
# It is raised when a drop operation in the game of Qi is invalid.
|
8
|
+
#
|
9
|
+
# @see IndexError
|
5
10
|
class Drop < ::IndexError
|
6
11
|
end
|
7
12
|
end
|
data/lib/qi.rb
CHANGED
@@ -3,45 +3,84 @@
|
|
3
3
|
require "digest"
|
4
4
|
require_relative "qi/error/drop"
|
5
5
|
|
6
|
-
#
|
6
|
+
# Qi is a class for representing a state of games like Shogi. It includes
|
7
|
+
# information about the current turn, captures, game board, and other game options.
|
7
8
|
class Qi
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# @
|
19
|
-
|
20
|
-
|
21
|
-
# @
|
22
|
-
|
23
|
-
|
9
|
+
# Constants for representing the North and South players.
|
10
|
+
North = :North
|
11
|
+
South = :South
|
12
|
+
|
13
|
+
# @return [Array] the pieces captured by the north player
|
14
|
+
attr_reader :north_captures
|
15
|
+
|
16
|
+
# @return [Array] the pieces captured by the south player
|
17
|
+
attr_reader :south_captures
|
18
|
+
|
19
|
+
# @return [Hash] the current state of the game board
|
20
|
+
attr_reader :squares
|
21
|
+
|
22
|
+
# @return [Hash] additional game options
|
23
|
+
attr_reader :options
|
24
|
+
|
25
|
+
# Initialize a new Qi object.
|
26
|
+
#
|
27
|
+
# @example Creating a new Qi object
|
28
|
+
# qi = Qi.new(true, ['P', 'G'], ['B', '+B'], {56 => 'P', 3 => 'g', 64 => '+B'}, fullmove_number: 42, is_in_check: false)
|
29
|
+
#
|
30
|
+
# @param [Boolean] is_north_turn true if it's the north player's turn, false otherwise
|
31
|
+
# @param [Array] north_captures the pieces captured by the north player
|
32
|
+
# @param [Array] south_captures the pieces captured by the south player
|
33
|
+
# @param [Hash] squares the current state of the game board
|
34
|
+
# @param [Hash] options additional game options
|
35
|
+
def initialize(is_north_turn, north_captures, south_captures, squares, **options)
|
24
36
|
@is_north_turn = is_north_turn
|
25
37
|
@north_captures = north_captures.sort
|
26
38
|
@south_captures = south_captures.sort
|
27
39
|
@squares = squares.compact
|
40
|
+
@options = options
|
28
41
|
end
|
29
42
|
|
30
|
-
#
|
43
|
+
# Apply a move or a drop on the board.
|
44
|
+
#
|
45
|
+
# The method creates a new instance of Qi representing the state of the game after the move.
|
46
|
+
# This includes updating the captures and the positions of the pieces on the board.
|
47
|
+
#
|
48
|
+
# @param src_square [Object, nil] the source square, or nil if dropping a piece from hand
|
49
|
+
# @param dst_square [Object] the destination square
|
50
|
+
# @param piece_name [String] the name of the piece to move
|
51
|
+
# @param in_hand [String, nil] the piece in hand, or nil if moving a piece from a square
|
52
|
+
# @param options [Hash] options to pass to the new instance
|
53
|
+
#
|
54
|
+
# @example Applying a move
|
55
|
+
# qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
56
|
+
# qi.commit(56, 47, 'P', nil) #=> <Qi South-turn===B,P,+B,G===47:P,64:+B>
|
31
57
|
#
|
32
|
-
# @
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
58
|
+
# @example Applying a capture
|
59
|
+
# qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
60
|
+
# qi.commit(56, 47, 'P', 'G') #=> <Qi South-turn===B,G,P,+B,G===47:P,64:+B>
|
61
|
+
#
|
62
|
+
# @example Applying a drop
|
63
|
+
# qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
64
|
+
# qi.commit(nil, 47, 'G', 'G') #=> <Qi South-turn===B,P,+B,G===47:G,56:P,64:+B>
|
65
|
+
#
|
66
|
+
# @return [Qi] the new game state after the move
|
67
|
+
def commit(src_square, dst_square, piece_name, in_hand, **options)
|
68
|
+
raise ::ArgumentError, "Both src_square and in_hand cannot be nil" if src_square.nil? && in_hand.nil?
|
69
|
+
|
70
|
+
modified_captures = update_captures(src_square, in_hand)
|
71
|
+
modified_squares = squares.merge({ src_square => nil, dst_square => piece_name })
|
72
|
+
self.class.new(south_turn?, *modified_captures, modified_squares, **options)
|
43
73
|
end
|
44
74
|
|
75
|
+
# Compare this Qi object with another for equality.
|
76
|
+
#
|
77
|
+
# @example Comparing two Qi objects
|
78
|
+
# qi1 = Qi.new(...)
|
79
|
+
# qi2 = Qi.new(...)
|
80
|
+
# qi1.eql?(qi2) #=> true or false
|
81
|
+
#
|
82
|
+
# @param [Qi] other the other Qi object to compare with
|
83
|
+
# @return [Boolean] true if the two objects represent the same game state, false otherwise
|
45
84
|
def eql?(other)
|
46
85
|
return false unless other.respond_to?(:serialize)
|
47
86
|
|
@@ -49,130 +88,180 @@ class Qi
|
|
49
88
|
end
|
50
89
|
alias == eql?
|
51
90
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
91
|
+
# Get the captures of the current player.
|
92
|
+
#
|
93
|
+
# The method returns the north player's captures when it's their turn,
|
94
|
+
# and the south player's captures when it's their turn.
|
95
|
+
#
|
96
|
+
# @example When it's the north player's turn
|
97
|
+
# qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
98
|
+
# qi.current_captures #=> ['B', 'P']
|
99
|
+
#
|
100
|
+
# @example When it's the south player's turn
|
101
|
+
# qi = Qi.new(false, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
102
|
+
# qi.current_captures #=> ['+B', 'G']
|
103
|
+
#
|
104
|
+
# @return [Array] the captures of the current player
|
105
|
+
def current_captures
|
106
|
+
north_turn? ? north_captures : south_captures
|
58
107
|
end
|
59
108
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
109
|
+
# Get the captures of the opponent player.
|
110
|
+
#
|
111
|
+
# @return [Array] the captures of the opponent player
|
112
|
+
def opponent_captures
|
113
|
+
north_turn? ? south_captures : north_captures
|
66
114
|
end
|
67
115
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
116
|
+
# Get the current turn.
|
117
|
+
#
|
118
|
+
# @return [Symbol] :North if it's the north player's turn, :South otherwise
|
119
|
+
def current_turn
|
120
|
+
north_turn? ? North : South
|
74
121
|
end
|
75
122
|
|
76
|
-
#
|
123
|
+
# Get the next turn.
|
77
124
|
#
|
78
|
-
# @return [
|
125
|
+
# @return [Symbol] :South if it's the north player's turn, :North otherwise
|
126
|
+
def next_turn
|
127
|
+
north_turn? ? South : North
|
128
|
+
end
|
129
|
+
|
130
|
+
# Check if it's the north player's turn.
|
131
|
+
#
|
132
|
+
# @return [Boolean] true if it's the north player's turn, false otherwise
|
79
133
|
def north_turn?
|
80
134
|
@is_north_turn
|
81
135
|
end
|
82
136
|
|
83
|
-
#
|
137
|
+
# Check if it's the south player's turn.
|
84
138
|
#
|
85
|
-
# @return [Boolean] true if it
|
139
|
+
# @return [Boolean] true if it's the south player's turn, false otherwise
|
86
140
|
def south_turn?
|
87
141
|
!north_turn?
|
88
142
|
end
|
89
143
|
|
90
|
-
#
|
144
|
+
# Convert the state to an array.
|
145
|
+
#
|
146
|
+
# @example Converting the state to an array
|
147
|
+
# qi.to_a #=> [true, ['P', 'G'], ['B', '+B'], {56 => 'P', 3 => 'g', 64 => '+B'}, {}]
|
91
148
|
#
|
92
|
-
# @return [Array
|
93
|
-
# - a boolean value indicating whose turn it is
|
94
|
-
# - an array of pieces captured by the north player
|
95
|
-
# - an array of pieces captured by the south player
|
96
|
-
# - a hash of squares on the board
|
149
|
+
# @return [Array] an array representing the game state
|
97
150
|
def to_a
|
98
151
|
[
|
99
152
|
north_turn?,
|
100
153
|
north_captures,
|
101
154
|
south_captures,
|
102
|
-
squares
|
155
|
+
squares,
|
156
|
+
options
|
103
157
|
]
|
104
158
|
end
|
105
159
|
|
106
|
-
#
|
160
|
+
# Convert the state to a hash.
|
107
161
|
#
|
108
|
-
# @
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# - squares: a hash of squares on the board
|
162
|
+
# @example Converting the state to a hash
|
163
|
+
# qi.to_h #=> {is_north_turn: true, north_captures: ['P', 'G'], south_captures: ['B', '+B'], squares: {56 => 'P', 3 => 'g', 64 => '+B'}, options: {}}
|
164
|
+
#
|
165
|
+
# @return [Hash] a hash representing the game state
|
113
166
|
def to_h
|
114
167
|
{
|
115
168
|
is_north_turn: north_turn?,
|
116
169
|
north_captures:,
|
117
170
|
south_captures:,
|
118
|
-
squares
|
171
|
+
squares:,
|
172
|
+
options:
|
119
173
|
}
|
120
174
|
end
|
121
175
|
|
122
|
-
#
|
176
|
+
# Generate a hash value for the game state.
|
177
|
+
#
|
178
|
+
# @example Generating a hash value
|
179
|
+
# qi.hash #=> "10dfb778f6559dd368c510a27e3b00bdb5b4ad88d4d67f38864d7e36de7c2f9c"
|
180
|
+
#
|
181
|
+
# @return [String] a SHA256 hash of the serialized game state
|
123
182
|
def hash
|
124
183
|
::Digest::SHA256.hexdigest(serialize)
|
125
184
|
end
|
126
185
|
|
127
|
-
#
|
186
|
+
# Serialize the game state.
|
128
187
|
#
|
129
|
-
# @
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
188
|
+
# @example Serializing the game state
|
189
|
+
# qi.serialize #=> "North-turn===P,G,B,+B===3:g,56:P,64:+B"
|
190
|
+
#
|
191
|
+
# @return [String] a string representation of the game state
|
133
192
|
def serialize
|
134
|
-
serialized_turn = "#{
|
135
|
-
serialized_captures = (north_captures + south_captures).
|
136
|
-
serialized_squares = squares.keys.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
|
193
|
+
serialized_turn = "#{current_turn}-turn"
|
194
|
+
serialized_captures = (north_captures + south_captures).join(",")
|
195
|
+
serialized_squares = squares.keys.sort.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
|
137
196
|
|
138
197
|
"#{serialized_turn}===#{serialized_captures}===#{serialized_squares}"
|
139
198
|
end
|
140
199
|
|
141
|
-
#
|
200
|
+
# Provide a string representation of the game state for debugging.
|
201
|
+
#
|
202
|
+
# @example Getting a string representation of the game state
|
203
|
+
# qi.inspect #=> "<Qi North-turn===P,G,B,+B===3:g,56:P,64:+B>"
|
142
204
|
#
|
143
|
-
# @return [String] a string
|
205
|
+
# @return [String] a string representation of the game state
|
144
206
|
def inspect
|
145
207
|
"<#{self.class} #{serialize}>"
|
146
208
|
end
|
147
209
|
|
148
210
|
private
|
149
211
|
|
150
|
-
#
|
212
|
+
# Update captures based on the source square and the piece in hand.
|
151
213
|
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
|
214
|
+
# If the source square is not nil and in_hand is nil (i.e., we're moving a piece on the board),
|
215
|
+
# the captures remain the same. If the source square is not nil and in_hand is not nil
|
216
|
+
# (i.e., we're capturing a piece that will remain in hand), the piece is added to the
|
217
|
+
# current player's captures. If the source square is nil (i.e., we're dropping a piece from hand),
|
218
|
+
# the piece is removed from the current player's captures.
|
219
|
+
#
|
220
|
+
# @param src_square [Object, nil] the source square, or nil if dropping a piece from hand
|
221
|
+
# @param in_hand [String, nil] the piece in hand, or nil if moving a piece from a square
|
222
|
+
#
|
223
|
+
# @example When moving a piece on the board
|
224
|
+
# qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
225
|
+
# qi.send(:update_captures, 56, nil) #=> [['B', 'P'], ['G', '+B']]
|
226
|
+
#
|
227
|
+
# @example When capturing a piece that will remain in hand
|
228
|
+
# qi = Qi.new(true, ['P', 'B'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
229
|
+
# qi.send(:update_captures, 56, 'G') #=> [['B', 'P', 'G'], ['G', '+B']]
|
230
|
+
#
|
231
|
+
# @example When dropping a piece from hand
|
232
|
+
# qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
233
|
+
# qi.send(:update_captures, nil, 'G') #=> [['B', 'P'], ['G', '+B']]
|
234
|
+
#
|
235
|
+
# @return [Array] a two-element array with the updated north and south captures
|
236
|
+
def update_captures(src_square, in_hand)
|
156
237
|
return [north_captures, south_captures] if in_hand.nil?
|
157
238
|
|
158
|
-
captures = if
|
159
|
-
remove_from_captures(in_hand, *
|
239
|
+
captures = if src_square.nil?
|
240
|
+
remove_from_captures(in_hand, *current_captures)
|
160
241
|
else
|
161
|
-
|
242
|
+
current_captures + [in_hand]
|
162
243
|
end
|
163
244
|
|
164
|
-
north_turn? ? [captures,
|
245
|
+
north_turn? ? [captures, opponent_captures] : [opponent_captures, captures]
|
165
246
|
end
|
166
247
|
|
167
|
-
# Removes
|
248
|
+
# Removes a piece from the captures.
|
249
|
+
#
|
250
|
+
# If the piece is not found in the captures, an Error::Drop exception is raised.
|
251
|
+
#
|
252
|
+
# @param piece [String] the piece to remove from the captures
|
253
|
+
# @param captures [Array] the captures to update
|
254
|
+
#
|
255
|
+
# @example
|
256
|
+
# qi = Qi.new(true, ['P', 'B', 'G'], ['G', '+B'], {56 => 'P', 64 => '+B'})
|
257
|
+
# qi.send(:remove_from_captures, 'G', *qi.current_captures) #=> ['P', 'B']
|
258
|
+
#
|
259
|
+
# @raise [Error::Drop] if the piece is not found in the captures
|
168
260
|
#
|
169
|
-
# @
|
170
|
-
# @param captures [Array<Object>] the array of captures
|
171
|
-
# @return [Array<Object>] the modified array of captures
|
172
|
-
# @raise [Qi::Error::Drop] if the piece is not found in the array
|
261
|
+
# @return [Array] the captures after removing the piece
|
173
262
|
def remove_from_captures(piece, *captures)
|
174
263
|
index = captures.rindex(piece)
|
175
|
-
raise Error::Drop, "There are no #{piece} in hand
|
264
|
+
raise Error::Drop, "There are no #{piece} in hand" if index.nil?
|
176
265
|
|
177
266
|
captures.delete_at(index)
|
178
267
|
captures
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 10.0.0.
|
4
|
+
version: 10.0.0.beta11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An abstraction that could help to update positions for games like Shogi.
|
14
14
|
email: contact@cyril.email
|
@@ -40,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: 1.3.1
|
42
42
|
requirements: []
|
43
|
-
rubygems_version: 3.4.
|
43
|
+
rubygems_version: 3.4.10
|
44
44
|
signing_key:
|
45
45
|
specification_version: 4
|
46
46
|
summary: An abstraction that could help to update positions for games like Shogi.
|