qi 10.0.0.beta9 → 10.0.0.beta10
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 +11 -9
- data/lib/qi/error/drop.rb +5 -0
- data/lib/qi.rb +153 -70
- 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: a009476d5905ba232aed52cafd6f8331632167af3a56c331be1d057b4a1ec964
|
4
|
+
data.tar.gz: f9a652c101b851e7e07d3b5fd69df6cb1965351697163e73c9600ff4b05251f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31c3aa57ebd829dcf24ca9604ef9a17dfb6a44fd0fd727dae76a730d3908c2f8cd2ba17d6dbc037ca9de88ac5913b4b4e759a967426d3e0c39b2100abb4891b1
|
7
|
+
data.tar.gz: 7b95a0bfdddd2be4fcdda01b64b2e42fc120f84d8becc5a5561383a41d88394a8d3a4e7b58a6d0721505d316c5e6e1357a0ddfff28f4179dec25c6af8e4b7688
|
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.beta10"
|
17
17
|
```
|
18
18
|
|
19
19
|
And then execute:
|
@@ -38,37 +38,39 @@ north_captures = %w[r r b g g g g s n n n n p p p p p p p p p p p p p p p p p]
|
|
38
38
|
south_captures = %w[S]
|
39
39
|
squares = { 3 => "s", 4 => "k", 5 => "s", 22 => "+P", 43 => "+B" }
|
40
40
|
|
41
|
-
qi0 = Qi.new(is_north_turn, north_captures, south_captures, squares)
|
41
|
+
qi0 = Qi.new(is_north_turn, north_captures, south_captures, squares, false)
|
42
42
|
|
43
43
|
qi0.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"]
|
44
44
|
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
|
48
|
+
qi0.serialize # => "South-turn===S,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===3:s,4:k,5:s,22:+P,43:+B===not-in-check"
|
49
|
+
qi0.inspect # => "<Qi South-turn===S,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===3:s,4:k,5:s,22:+P,43:+B===not-in-check>"
|
50
50
|
|
51
51
|
qi0.to_a
|
52
52
|
# [false,
|
53
53
|
# ["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
54
|
# ["S"],
|
55
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"}
|
55
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 43=>"+B"},
|
56
|
+
# false]
|
56
57
|
|
57
|
-
qi1 = qi0.commit({ 43 => nil, 13 => "+B" })
|
58
|
+
qi1 = qi0.commit({ 43 => nil, 13 => "+B" }, nil, is_drop: nil, is_in_check: true)
|
58
59
|
|
59
60
|
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
61
|
qi1.south_captures # => ["S"]
|
61
62
|
qi1.squares # => {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
62
63
|
qi1.north_turn? # => true
|
63
64
|
qi1.south_turn? # => false
|
64
|
-
qi1.serialize # => "
|
65
|
-
qi1.inspect # => "<Qi
|
65
|
+
qi1.serialize # => "North-turn===S,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===3:s,4:k,5:s,22:+P,13:+B===in-check"
|
66
|
+
qi1.inspect # => "<Qi North-turn===S,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===3:s,4:k,5:s,22:+P,13:+B===in-check>"
|
66
67
|
|
67
68
|
qi1.to_a
|
68
69
|
# [true,
|
69
70
|
# ["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
71
|
# ["S"],
|
71
|
-
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"}
|
72
|
+
# {3=>"s", 4=>"k", 5=>"s", 22=>"+P", 13=>"+B"},
|
73
|
+
# true]
|
72
74
|
```
|
73
75
|
|
74
76
|
## 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,69 @@
|
|
3
3
|
require "digest"
|
4
4
|
require_relative "qi/error/drop"
|
5
5
|
|
6
|
-
#
|
6
|
+
# The Qi class represents the current state of a game, tracking both the positions of pieces on the board and the pieces captured by each player.
|
7
|
+
# It provides methods for manipulating the game state, including moving pieces around the board, capturing opponent's pieces, and dropping pieces from hand onto the board.
|
8
|
+
# Additionally, it maintains information about the current game turn (which player's turn it is) and whether a player is in check.
|
7
9
|
class Qi
|
10
|
+
# Constant representing the North player.
|
11
|
+
North = "North"
|
12
|
+
|
13
|
+
# Constant representing the South player.
|
14
|
+
South = "South"
|
15
|
+
|
8
16
|
# @!attribute [r] north_captures
|
9
|
-
#
|
17
|
+
# @return [Array] The list of pieces captured by the North player.
|
18
|
+
attr_reader :north_captures
|
19
|
+
|
10
20
|
# @!attribute [r] south_captures
|
11
|
-
#
|
21
|
+
# @return [Array] The list of pieces captured by the South player.
|
22
|
+
attr_reader :south_captures
|
23
|
+
|
12
24
|
# @!attribute [r] squares
|
13
|
-
#
|
14
|
-
attr_reader :
|
25
|
+
# @return [Hash] The current state of the board, represented as a hash where each key is a position and each value is the state of that position.
|
26
|
+
attr_reader :squares
|
15
27
|
|
16
|
-
# Initializes a new
|
28
|
+
# Initializes a new instance of the game state.
|
17
29
|
#
|
18
|
-
# @param is_north_turn [Boolean]
|
19
|
-
# @param north_captures [Array
|
20
|
-
# @param south_captures [Array
|
21
|
-
# @param squares [Hash
|
22
|
-
|
23
|
-
|
30
|
+
# @param is_north_turn [Boolean] True if it's North's turn, false otherwise.
|
31
|
+
# @param north_captures [Array] An array representing the pieces captured by North.
|
32
|
+
# @param south_captures [Array] An array representing the pieces captured by South.
|
33
|
+
# @param squares [Hash] A hash representing the state of the squares on the board.
|
34
|
+
# @param is_in_check [Boolean] True if the current player is in check, false otherwise.
|
35
|
+
def initialize(is_north_turn, north_captures, south_captures, squares, is_in_check)
|
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
|
+
@is_in_check = is_in_check
|
28
41
|
end
|
29
42
|
|
30
|
-
#
|
43
|
+
# Creates a new game state by applying a set of diffs to the squares and the captures.
|
44
|
+
# Raises an error if in_hand is provided but is_drop is not, or vice versa.
|
31
45
|
#
|
32
|
-
# @param diffs [Hash
|
33
|
-
# @param in_hand [
|
34
|
-
# @param is_drop [Boolean]
|
35
|
-
# @
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
46
|
+
# @param diffs [Hash] A hash representing the changes to the squares. Each key is a position, and each value is the new state of that position.
|
47
|
+
# @param in_hand [String, nil] A string representing the piece that the current player has in hand, or nil if no piece is in hand.
|
48
|
+
# @param is_drop [Boolean, nil] True if the current player is dropping a piece, false if they are not, or nil if there is no piece in hand.
|
49
|
+
# @param is_in_check [Boolean] True if the current player is in check after the move, false otherwise.
|
50
|
+
# @return [Qi] The new game state after applying the diffs.
|
51
|
+
# @raise [ArgumentError] If in_hand is provided but is_drop is not, or vice versa.
|
52
|
+
def commit(diffs, in_hand, is_drop:, is_in_check:)
|
53
|
+
if !in_hand.nil? && is_drop.nil?
|
54
|
+
raise ::ArgumentError, "A piece is in hand, but is_drop is not provided"
|
55
|
+
elsif in_hand.nil? && !is_drop.nil?
|
56
|
+
raise ::ArgumentError, "No piece is in hand, but is_drop is provided"
|
57
|
+
end
|
58
|
+
|
40
59
|
modified_squares = squares.merge(diffs)
|
41
60
|
modified_captures = update_captures(in_hand, is_drop:)
|
42
|
-
self.class.new(
|
61
|
+
self.class.new(south_turn?, *modified_captures, modified_squares, is_in_check)
|
43
62
|
end
|
44
63
|
|
64
|
+
# Checks if this game state is equal to another.
|
65
|
+
# Two game states are considered equal if they can be serialized to the same string.
|
66
|
+
#
|
67
|
+
# @param other [Object] The object to compare with this game state.
|
68
|
+
# @return [Boolean] True if the other object can be serialized and its serialized form is equal to this game state's serialized form, false otherwise.
|
45
69
|
def eql?(other)
|
46
70
|
return false unless other.respond_to?(:serialize)
|
47
71
|
|
@@ -49,7 +73,21 @@ class Qi
|
|
49
73
|
end
|
50
74
|
alias == eql?
|
51
75
|
|
52
|
-
|
76
|
+
# Returns the captures of the current player.
|
77
|
+
#
|
78
|
+
# @return [Array] An array or other iterable representing the pieces captured by the current player.
|
79
|
+
def current_captures
|
80
|
+
if north_turn?
|
81
|
+
north_captures
|
82
|
+
else
|
83
|
+
south_captures
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the list of pieces that the current player's opponent has captured.
|
88
|
+
#
|
89
|
+
# @return [Array] An array of pieces that the opponent has captured.
|
90
|
+
def opponent_captures
|
53
91
|
if north_turn?
|
54
92
|
south_captures
|
55
93
|
else
|
@@ -57,65 +95,98 @@ class Qi
|
|
57
95
|
end
|
58
96
|
end
|
59
97
|
|
60
|
-
|
98
|
+
# Returns the name of the current side.
|
99
|
+
#
|
100
|
+
# @return [String] "North" if it's North's turn, "South" otherwise.
|
101
|
+
def current_turn
|
61
102
|
if north_turn?
|
62
|
-
|
103
|
+
North
|
63
104
|
else
|
64
|
-
|
105
|
+
South
|
65
106
|
end
|
66
107
|
end
|
67
108
|
|
68
|
-
|
109
|
+
# Returns the name of the next side.
|
110
|
+
# If it's currently the North player's turn, this method will return "South", and vice versa.
|
111
|
+
#
|
112
|
+
# @return [String] "South" if it's North's turn, "North" otherwise.
|
113
|
+
def next_turn
|
69
114
|
if north_turn?
|
70
|
-
|
115
|
+
South
|
71
116
|
else
|
72
|
-
|
117
|
+
North
|
73
118
|
end
|
74
119
|
end
|
75
120
|
|
76
|
-
# Checks if it
|
121
|
+
# Checks if it's North's turn.
|
77
122
|
#
|
78
|
-
# @return [Boolean]
|
123
|
+
# @return [Boolean] True if it's North's turn, false otherwise.
|
79
124
|
def north_turn?
|
80
125
|
@is_north_turn
|
81
126
|
end
|
82
127
|
|
83
|
-
# Checks if it
|
128
|
+
# Checks if it's South's turn.
|
84
129
|
#
|
85
|
-
# @return [Boolean]
|
130
|
+
# @return [Boolean] True if it's South's turn, false otherwise.
|
86
131
|
def south_turn?
|
87
132
|
!north_turn?
|
88
133
|
end
|
89
134
|
|
90
|
-
#
|
135
|
+
# Checks if the current player is in check.
|
136
|
+
#
|
137
|
+
# @return [Boolean] True if the current player is in check, false otherwise.
|
138
|
+
def in_check?
|
139
|
+
@is_in_check
|
140
|
+
end
|
141
|
+
|
142
|
+
# Checks if the current player is not in check.
|
143
|
+
#
|
144
|
+
# @return [Boolean] True if the current player is not in check, false otherwise.
|
145
|
+
def not_in_check?
|
146
|
+
!in_check?
|
147
|
+
end
|
148
|
+
|
149
|
+
# Converts the current game state to an array. The resulting array includes:
|
150
|
+
# whether it's North's turn, the pieces captured by North, the pieces captured by South,
|
151
|
+
# the state of the squares, and whether the current player is in check.
|
152
|
+
# This array can be used for various purposes, such as saving the game state,
|
153
|
+
# transmitting the game state over a network, or analyzing the game state.
|
91
154
|
#
|
92
|
-
# @return [Array
|
93
|
-
# -
|
94
|
-
# -
|
95
|
-
# -
|
96
|
-
# -
|
155
|
+
# @return [Array] The game state, represented as an array. The elements of the array are:
|
156
|
+
# - A boolean indicating whether it's North's turn,
|
157
|
+
# - An array or other iterable representing the pieces captured by North,
|
158
|
+
# - An array or other iterable representing the pieces captured by South,
|
159
|
+
# - A data structure representing the state of the squares on the board,
|
160
|
+
# - A boolean indicating whether the current player is in check.
|
97
161
|
def to_a
|
98
162
|
[
|
99
163
|
north_turn?,
|
100
164
|
north_captures,
|
101
165
|
south_captures,
|
102
|
-
squares
|
166
|
+
squares,
|
167
|
+
in_check?
|
103
168
|
]
|
104
169
|
end
|
105
170
|
|
106
|
-
#
|
171
|
+
# Converts the current game state to a hash. The resulting hash includes:
|
172
|
+
# whether it's North's turn, the pieces captured by North, the pieces captured by South,
|
173
|
+
# the state of the squares, and whether the current player is in check.
|
174
|
+
# This hash can be used for various purposes, such as saving the game state,
|
175
|
+
# transmitting the game state over a network, or analyzing the game state.
|
107
176
|
#
|
108
|
-
# @return [Hash
|
109
|
-
# - is_north_turn
|
110
|
-
# - north_captures
|
111
|
-
# - south_captures
|
112
|
-
# - squares
|
177
|
+
# @return [Hash] The game state, represented as a hash. The keys of the hash are:
|
178
|
+
# - :is_north_turn, a boolean indicating whether it's North's turn,
|
179
|
+
# - :north_captures, an array or other iterable representing the pieces captured by North,
|
180
|
+
# - :south_captures, an array or other iterable representing the pieces captured by South,
|
181
|
+
# - :squares, a data structure representing the state of the squares on the board,
|
182
|
+
# - :is_in_check, a boolean indicating whether the current player is in check.
|
113
183
|
def to_h
|
114
184
|
{
|
115
185
|
is_north_turn: north_turn?,
|
116
186
|
north_captures:,
|
117
187
|
south_captures:,
|
118
|
-
squares
|
188
|
+
squares:,
|
189
|
+
is_in_check: in_check?
|
119
190
|
}
|
120
191
|
end
|
121
192
|
|
@@ -124,55 +195,67 @@ class Qi
|
|
124
195
|
::Digest::SHA256.hexdigest(serialize)
|
125
196
|
end
|
126
197
|
|
127
|
-
# Returns a
|
198
|
+
# Returns a sorted list of all pieces currently on the board.
|
199
|
+
#
|
200
|
+
# @return [Array] An array of all pieces currently on the board.
|
201
|
+
def board_pieces
|
202
|
+
squares.keys.sort
|
203
|
+
end
|
204
|
+
|
205
|
+
# Serialize the current game state to a string. The serialized state includes:
|
206
|
+
# the current turn, the captured pieces, the state of the squares, and whether
|
207
|
+
# the current player is in check. The serialized state can be used to save
|
208
|
+
# the game, or to transmit the game state over a network.
|
128
209
|
#
|
129
|
-
# @return [String]
|
130
|
-
# - the current turn, either "NorthTurn" or "SouthTurn"
|
131
|
-
# - the captures, sorted and joined by ","
|
132
|
-
# - the squares, mapped to "coordinate:piece" pairs and joined by ","
|
210
|
+
# @return [String] The serialized game state.
|
133
211
|
def serialize
|
134
|
-
serialized_turn = "#{
|
212
|
+
serialized_turn = "#{current_turn}-turn"
|
135
213
|
serialized_captures = (north_captures + south_captures).sort.join(",")
|
136
|
-
serialized_squares =
|
214
|
+
serialized_squares = board_pieces.map { |i| "#{i}:#{squares.fetch(i)}" }.join(",")
|
215
|
+
serialized_check = (in_check? ? "in-check" : "not-in-check")
|
137
216
|
|
138
|
-
"#{serialized_turn}===#{serialized_captures}===#{serialized_squares}"
|
217
|
+
"#{serialized_turn}===#{serialized_captures}===#{serialized_squares}===#{serialized_check}"
|
139
218
|
end
|
140
219
|
|
141
|
-
#
|
220
|
+
# Provides a string representation of the game state, including the class name and the serialized game state.
|
142
221
|
#
|
143
|
-
# @return [String]
|
222
|
+
# @return [String] A string representation of the game state.
|
144
223
|
def inspect
|
145
224
|
"<#{self.class} #{serialize}>"
|
146
225
|
end
|
147
226
|
|
148
227
|
private
|
149
228
|
|
150
|
-
# Updates the captures
|
229
|
+
# Updates the list of captures based on the piece in hand and whether the move is a drop.
|
230
|
+
# If the in_hand parameter is nil, the method returns the current captures without modification.
|
231
|
+
# Otherwise, the method either adds the piece in hand to the current player's captures (if is_drop is false),
|
232
|
+
# or removes the piece in hand from the current player's captures (if is_drop is true).
|
151
233
|
#
|
152
|
-
# @param in_hand [
|
153
|
-
# @param is_drop [Boolean]
|
154
|
-
# @return [Array]
|
234
|
+
# @param in_hand [String, nil] The piece in hand, or nil if no piece is in hand.
|
235
|
+
# @param is_drop [Boolean] True if the current move is a drop, false otherwise.
|
236
|
+
# @return [Array] The updated list of captures for North and South.
|
155
237
|
def update_captures(in_hand, is_drop:)
|
156
238
|
return [north_captures, south_captures] if in_hand.nil?
|
157
239
|
|
158
240
|
captures = if is_drop
|
159
|
-
remove_from_captures(in_hand, *
|
241
|
+
remove_from_captures(in_hand, *current_captures)
|
160
242
|
else
|
161
|
-
|
243
|
+
current_captures + [in_hand]
|
162
244
|
end
|
163
245
|
|
164
|
-
north_turn? ? [captures,
|
246
|
+
north_turn? ? [captures, opponent_captures] : [opponent_captures, captures]
|
165
247
|
end
|
166
248
|
|
167
|
-
# Removes
|
249
|
+
# Removes a piece from the captures.
|
250
|
+
# The method raises an error if the piece is not found in the captures.
|
168
251
|
#
|
169
|
-
# @param piece [
|
170
|
-
# @param captures [Array
|
171
|
-
# @return [Array
|
172
|
-
# @raise [
|
252
|
+
# @param piece [String] The piece to remove from the captures.
|
253
|
+
# @param captures [Array] The list of captures.
|
254
|
+
# @return [Array] The updated list of captures.
|
255
|
+
# @raise [Error::Drop] If the piece is not found in the captures.
|
173
256
|
def remove_from_captures(piece, *captures)
|
174
257
|
index = captures.rindex(piece)
|
175
|
-
raise Error::Drop, "There are no #{piece} in hand
|
258
|
+
raise Error::Drop, "There are no #{piece} in hand" if index.nil?
|
176
259
|
|
177
260
|
captures.delete_at(index)
|
178
261
|
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.beta10
|
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-11 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.
|